# Instalacion de los cierres en un Mac mini dedicado

## Control del documento

| Campo | Valor |
|---|---|
| Proyecto | Dashboard Talento Humano - Faranda Hotels |
| Repositorio | `https://git.faranda.online/faranda/rrhh-report.git` |
| Rama de produccion | `main` |
| Base historica destino | `Base de Datos Historicos Talento Humano` |
| Servidor PostgreSQL | `172.19.0.160:5432` |
| Schema | `public` |
| Zona horaria funcional | `America/Bogota` |
| Estado | Listo para instalacion controlada |

Este documento es la variante para un **Mac mini dedicado** de
`deploy/INSTALACION_JOB_SERVIDOR_API.md` (esa version asume un servidor
Linux con `cron`). Aqui el mecanismo es **launchd** (LaunchAgents), el mismo
que ya usa este Mac de trabajo hoy para `cierre_mes.py` y
`cargar_historico_nomina_pyg.py` — solo que corriendo en una maquina
dedicada que se mantiene encendida, en vez de la Mac que usas a diario.

## 1. Objetivo

Sacar los cierres mensuales de la Mac de trabajo diario para que:

- la ejecucion no dependa de que tu Mac este encendida a las 5:00 AM;
- los cierres se escriban en PostgreSQL remoto `172.19.0.160` (ya es el
  destino actual, no cambia);
- las llaves de API y la clave de PostgreSQL no queden expuestas en un
  equipo de uso diario;
- exista un procedimiento verificable de instalacion, certificacion,
  operacion y reversa.

## 2. Resultado esperado

```text
GET/POST NominaApp -------------+
                                 |
POST P&G Faranda -----------------+--> LaunchAgents en el Mac mini
                                              |
                                              v
                                PostgreSQL 172.19.0.160
                          Base de Datos Historicos Talento Humano
                                              |
                                              v
                                 Dashboard Talento Humano
```

El Mac mini ejecuta los jobs. Tu Mac de trabajo deja de correrlos una vez
certificado el corte (seccion 12).

## 3. Por que launchd y no cron

- Es exactamente el mecanismo que ya esta probado en produccion en este
  proyecto (`~/Library/LaunchAgents/com.bia.*`, ver
  `project_dashboard.md`).
- No requiere instalar ni administrar `cron` en macOS (Apple lo desalienta
  desde hace anos).
- `StartCalendarInterval` reproduce exactamente los mismos horarios
  (dia 1/5/7, 5:00 y 5:30 AM).

## 4. Requisitos del Mac mini

### 4.1 Hardware / sistema

- Mac mini con alimentacion permanente (no laptop con bateria).
- macOS con Python 3.10+ (Homebrew recomendado: `brew install python3`).
- Espacio para el repositorio y logs.

### 4.2 Que se mantenga siempre disponible

El Mac mini debe estar **encendido y con la sesion del usuario tecnico
iniciada** las 24 horas (los LaunchAgents de usuario solo corren mientras
esa sesion esta abierta):

1. **Ajustes → Bloqueo de pantalla / Usuarios y grupos**: activar
   **inicio de sesion automatico** para el usuario tecnico (ej. `rrhhjobs`).
2. **Ajustes → Energia**:
   - Desactivar "Poner el Mac en reposo automaticamente" (o ponerlo en
     "Nunca") cuando esta conectado a la corriente.
   - Activar "Activar para acceso a la red" (Wake for network access).
   - Si el modelo lo permite, activar "Iniciar automaticamente despues de
     un corte de energia".
3. Desactivar el protector de pantalla con bloqueo, o al menos confirmar
   que no cierra la sesion del usuario tecnico.
4. Conectar el Mac mini por cable Ethernet si es posible (mas estable que
   Wi-Fi para un equipo que nadie supervisa a diario).

### 4.3 Red

| Origen | Destino | Puerto | Proposito |
|---|---|---:|---|
| Mac mini | `172.19.0.160` | 5432 | Escritura de historicos |
| Mac mini | Git Faranda | 443 | Descargar y actualizar codigo |
| Mac mini | API de nomina (`api.innfinancial.cloud`) | 443 | Cierres de empleados, IR y nomina |
| Mac mini | `app.faranda.online` | 443 | Cierre P&G |

PostgreSQL no debe quedar abierto a internet; confirmar que el Mac mini
tiene ruta hacia `172.19.0.160` (VPN, red interna o IP publica autorizada
segun como este configurado ese servidor hoy).

### 4.4 Usuario tecnico

Crear (o usar) una cuenta macOS dedicada para estos jobs, distinta de tu
cuenta de uso diario si el Mac mini se comparte para otras cosas:

```bash
sudo sysadminctl -addUser rrhhjobs -fullName "RRHH Jobs" -password 'CLAVE_SEGURA'
```

No es obligatorio si el Mac mini es de uso exclusivo para esto — en ese
caso se puede usar la cuenta principal del equipo.

## 5. Descarga del repositorio

```bash
mkdir -p ~/rrhh-report
git clone https://git.faranda.online/faranda/rrhh-report.git ~/rrhh-report
cd ~/rrhh-report
git checkout main
git status
git log -1 --oneline
```

El estado debe indicar la rama `main` sin modificaciones locales.

## 6. Dependencias Python

```bash
cd ~/rrhh-report
python3 -m pip install --upgrade pip
python3 -m pip install psycopg2-binary pandas openpyxl
python3 -c "import psycopg2, pandas, openpyxl; print('dependencias OK')"
```

Si usas Homebrew Python, confirma la ruta exacta (la necesitas en el
paso 8):

```bash
command -v python3
```

## 7. Archivo privado `.env`

```bash
cd ~/rrhh-report
touch .env
chmod 600 .env
nano .env
```

Plantilla (usa las mismas credenciales que ya funcionan en la Mac de
trabajo — estan en su `.env`, no se repiten aqui por seguridad):

```dotenv
DATABASE_URL=postgresql+psycopg://usuario:password@172.19.0.160:5432/Base%20de%20Datos%20Historicos%20Talento%20Humano
PG_SCHEMA=public

NOMINA_API_BASE=https://api.innfinancial.cloud/api/v1
NOMINA_API_KEY=[SECRETO_NOMINA]

PYG_API_URL=https://app.faranda.online/rm/reportePyg/comparativo/
PYG_API_KEY=[SECRETO_PYG]

APP_VERIFY_SSL=true
APP_DEBUG_ERRORS=false
```

Reglas:

- codificar caracteres especiales de usuario/password/nombre de base en la
  URL (espacios → `%20`, etc.);
- no reutilizar el usuario superadministrador de PostgreSQL si existe un
  rol de escritura dedicado (ver seccion 9 de
  `deploy/INSTALACION_JOB_SERVIDOR_API.md` para crear `th_cierre_writer`);
- no guardar el `.env` en Git — ya esta en `.gitignore`.

Verificar permisos:

```bash
stat -f '%Su %A %N' .env
```

Debe mostrar `600`.

## 8. Prueba en seco (`--dry-run`)

```bash
cd ~/rrhh-report
APP_DIR="$HOME/rrhh-report" \
PYTHON="$(command -v python3)" \
ENV_FILE="$HOME/rrhh-report/.env" \
LOG_DIR="$HOME/rrhh-report/logs/cloud" \
deploy/mac-mini/install-macmini-launchd.sh --dry-run
```

Esto valida (sin escribir nada ni instalar los LaunchAgents):

- que `.env` existe con las claves requeridas;
- que los scripts compilan;
- conexion a la base remota y conteos actuales
  (`historico_empleados`, `historico_ir`, `historico_nomina_pyg`);
- `cierre_mes.py --test` (modo lectura);
- `cargar_historico_nomina_pyg.py --dry-run` (modo lectura).

Revisar la salida antes de continuar. Si algo falla, corregir y repetir
el `--dry-run` — no seguir al paso 9 hasta que termine sin errores.

## 9. Instalacion de los LaunchAgents

Solo despues de aprobar el `--dry-run`:

```bash
cd ~/rrhh-report
APP_DIR="$HOME/rrhh-report" \
PYTHON="$(command -v python3)" \
ENV_FILE="$HOME/rrhh-report/.env" \
LOG_DIR="$HOME/rrhh-report/logs/cloud" \
deploy/mac-mini/install-macmini-launchd.sh --install
```

El instalador:

1. Copia las 6 plantillas de `deploy/mac-mini/launchagents/` a
   `~/Library/LaunchAgents/` en el Mac mini, sustituyendo las rutas de
   `APP_DIR` y `PYTHON`.
2. Descarga (si existia) y vuelve a cargar cada `LaunchAgent` con
   `launchctl load -w`.
3. Imprime al final el bloque a ejecutar **en tu Mac de trabajo** para
   apagar los LaunchAgents locales (seccion 12).

Jobs instalados:

| Label | Dia | Hora | Accion |
|---|---|---|---|
| `com.bia.cierre-mes-dia1` | 1 | 5:00 AM | Fotografia de empleados + IR |
| `com.bia.cierre-mes-dia5` | 5 | 5:00 AM | Recuperacion empleados (si vacio) + refresco IR |
| `com.bia.cierre-mes-dia7` | 7 | 5:00 AM | Recuperacion empleados (si vacio) + refresco IR |
| `com.bia.nomina-pyg-dia1` | 1 | 5:30 AM | Carga Nomina + P&G mes anterior |
| `com.bia.nomina-pyg-dia5` | 5 | 5:30 AM | Carga Nomina + P&G mes anterior |
| `com.bia.nomina-pyg-dia7` | 7 | 5:30 AM | Carga Nomina + P&G mes anterior |

Verificar que quedaron cargados:

```bash
launchctl list | grep com.bia
```

Deben aparecer los 6, con estado `-` (esperando su horario) o el ultimo
codigo de salida si ya corrieron.

## 10. Zona horaria

Los horarios (`StartCalendarInterval`) usan la hora local del Mac mini.
Confirmar antes de instalar:

```bash
date
sudo systemsetup -gettimezone
```

Si el Mac mini no esta en zona horaria Colombia
(`America/Bogota`), configurarla:

```bash
sudo systemsetup -settimezone America/Bogota
```

## 11. Certificacion en PostgreSQL

Con un usuario autorizado, mostrando solo agregados:

```sql
SELECT fecha, COUNT(*) AS empleados
FROM public.historico_empleados
GROUP BY fecha ORDER BY fecha DESC LIMIT 3;

SELECT periodo, COUNT(*) AS movimientos
FROM public.historico_ir
GROUP BY periodo ORDER BY periodo DESC LIMIT 3;

SELECT fuente, periodo, estado, COUNT(*) AS ejecuciones, SUM(filas) AS filas
FROM public.historico_nomina_pyg_control
GROUP BY fuente, periodo, estado
ORDER BY periodo DESC, fuente, estado;
```

No se considera completa la certificacion si existen controles con
`estado = 'error'` para el periodo que se esta cerrando.

Validar tambien desde el dashboard (en cualquier maquina donde corra
`servidor.py`):

```bash
curl -fsS http://127.0.0.1:8080/proxy/historico/max-periodo
```

## 12. Corte desde la Mac de trabajo al Mac mini

No deben existir dos equipos escribiendo el mismo cierre al mismo tiempo.

Procedimiento:

1. Aprobar el `--dry-run` y luego el `--install` en el Mac mini
   (secciones 8 y 9).
2. Confirmar con `launchctl list | grep com.bia` en el Mac mini que los 6
   LaunchAgents quedaron cargados.
3. **Antes de las 5:00 AM del primer dia real**, apagar los LaunchAgents
   equivalentes en tu Mac de trabajo (no borrar los `.plist`, solo
   descargarlos):

   ```bash
   launchctl unload ~/Library/LaunchAgents/com.bia.cierre-mes-dia1.plist
   launchctl unload ~/Library/LaunchAgents/com.bia.cierre-mes-dia5.plist
   launchctl unload ~/Library/LaunchAgents/com.bia.cierre-mes-dia7.plist
   launchctl unload ~/Library/LaunchAgents/com.bia.nomina-pyg-dia1.plist
   launchctl unload ~/Library/LaunchAgents/com.bia.nomina-pyg-dia5.plist
   launchctl unload ~/Library/LaunchAgents/com.bia.nomina-pyg-dia7.plist
   ```

   (El instalador imprime este mismo bloque al final del paso 9.)

4. **No desactivar** en tu Mac de trabajo:

   ```text
   com.bia.dashboard-talento
   com.bia.dashboard-talento-dev
   ```

   Esos siguen sirviendo el dashboard localmente; no hacen cierres.

5. Supervisar la primera ejecucion real desde el Mac mini.
6. Si el Mac mini falla, se puede reactivar temporalmente el LaunchAgent
   local con `launchctl load -w ~/Library/LaunchAgents/<label>.plist` como
   respaldo, y volver a apagarlo una vez resuelto.

## 13. Logs

```text
~/rrhh-report/logs/cloud/cierre_mes_dia1.log
~/rrhh-report/logs/cloud/cierre_mes_dia5_7.log
~/rrhh-report/logs/cloud/nomina_pyg.log
```

Logs detallados adicionales (generados por los propios scripts):

```text
~/rrhh-report/logs/cierres/
```

No enviar logs completos a canales publicos. Antes de compartir un
fragmento, revisar que no contenga cedulas, nombres, salarios, passwords,
tokens o respuestas crudas de API.

## 14. Actualizacion futura del job

Antes de actualizar, fuera de las ventanas de cierre (dias 1, 5 y 7):

```bash
cd ~/rrhh-report
git status
git log -1 --oneline
git pull --ff-only origin main
python3 -m pip install psycopg2-binary pandas openpyxl
```

Repetir el `--dry-run` (seccion 8) despues de actualizar. Si cambio algun
horario en las plantillas de `deploy/mac-mini/launchagents/`, volver a
correr `--install`.

## 15. Recuperacion ante fallas

### Falla de conexion a PostgreSQL

1. No cambiar credenciales en el codigo.
2. Comprobar conectividad: `nc -vz 172.19.0.160 5432`.
3. Revisar VPN o ruta interna del Mac mini.
4. Repetir el `--dry-run`.

### API de nomina o P&G no responde

1. Confirmar que la llave sigue vigente.
2. No imprimir la respuesta completa en logs compartidos.
3. Usar los dias 5 y 7 como recuperacion automatica (ya programados).

### El Mac mini se reinicio o perdio la sesion

1. Confirmar que el inicio de sesion automatico funciono
   (`who` deberia mostrar al usuario tecnico conectado).
2. `launchctl list | grep com.bia` — si no aparecen, la sesion no cargo
   los LaunchAgents; iniciar sesion manualmente una vez y revisar el
   inicio de sesion automatico.

### Se ejecutaron el Mac mini y la Mac de trabajo al mismo tiempo

1. Detener el scheduler duplicado (normalmente el de la Mac de trabajo,
   ver seccion 12).
2. No borrar datos inmediatamente.
3. Comparar conteos del periodo en PostgreSQL.
4. Ejecutar una unica recarga controlada si se requiere reconstruir el
   periodo (`cargar_historico_nomina_pyg.py --fuente ambas --desde YYYY-MM
   --hasta YYYY-MM`, es idempotente).

## 16. Reversa

Para detener los cierres en el Mac mini sin borrar codigo ni datos:

```bash
launchctl unload ~/Library/LaunchAgents/com.bia.cierre-mes-dia1.plist
launchctl unload ~/Library/LaunchAgents/com.bia.cierre-mes-dia5.plist
launchctl unload ~/Library/LaunchAgents/com.bia.cierre-mes-dia7.plist
launchctl unload ~/Library/LaunchAgents/com.bia.nomina-pyg-dia1.plist
launchctl unload ~/Library/LaunchAgents/com.bia.nomina-pyg-dia5.plist
launchctl unload ~/Library/LaunchAgents/com.bia.nomina-pyg-dia7.plist
```

Y reactivar los locales en la Mac de trabajo con `launchctl load -w`.

## 17. Criterios de aceptacion

El despliegue queda certificado cuando:

- el Mac mini permanece encendido, con sesion del usuario tecnico activa,
  sin reposo automatico;
- `.env` tiene permisos `600` y no esta en Git;
- el `--dry-run` termina sin errores;
- `launchctl list | grep com.bia` muestra los 6 LaunchAgents cargados;
- la zona horaria del Mac mini fue validada como `America/Bogota`;
- la Mac de trabajo tiene los 6 LaunchAgents de cierre **descargados**
  (no borrados, solo `unload`);
- el dia 1 crea el periodo esperado de empleados;
- dias 5 y 7 no reemplazan empleados existentes;
- Nomina y P&G no presentan controles en error para el periodo cerrado;
- `/proxy/historico/max-periodo` refleja el nuevo cierre;
- el dashboard funciona igual desde cualquier maquina, porque sigue
  leyendo de `172.19.0.160`.
