Docs PayMind
07
SRD Backend — PayMind
Arquitectura planilla multi-país, sync cross-módulo, integraciones CCSS/IMSS/DIAN
Versión
v1.0
Fecha
Mayo 2026
Audiencia
Founders · Dev
Estado
DRAFT
PAYMIND
Inteligencia que cuida a tu equipo. Sin errores, sin sustos, sin sorpresas.
Módulo #4 — Cheryx Suite

[CONSEJO — Doc 07] Contrarian: El backend de PayMind tiene un requerimiento inusual: el motor de planilla debe ser reproducible — si corres el mismo cálculo el día 1 y el día 365, el resultado debe ser idéntico dado el mismo input. Las tablas de tasas regulatorias deben estar versionadas con fechas de vigencia, no sobreescritas. Si no, una auditoría de la CCSS que pida "recalcula la planilla del período X" no puede responderse. Executor: El archivo bancario es el output más crítico de la planilla — es el archivo que realmente transfiere el dinero a los empleados. Un error en el formato del archivo ACH/SINPE puede resultar en que 50 empleados no reciban su salario. El formato debe ser validado contra las especificaciones oficiales de cada banco antes de cada deploy, no solo antes del lanzamiento. Auditor Seg.: Los datos de planilla (salario de cada empleado) son los datos más sensibles que PayMind procesa. Un atacante que acceda a la planilla de una empresa puede saber exactamente cuánto gana cada persona. Row-level security + column-level encryption en salarios es obligatorio.


1. Arquitectura general

┌─────────────────────────────────────────────────────────────────┐
│                        FRONTEND (Next.js)                        │
└────────────────────────────┬────────────────────────────────────┘
                             │ HTTPS + JWT httpOnly cookie
┌────────────────────────────▼────────────────────────────────────┐
│                    FastAPI (API Gateway)                         │
│  /api/v1/paymind/*                                             │
└──┬──────────────┬──────────────┬──────────────┬─────────────────┘
   │              │              │              │
   ▼              ▼              ▼              ▼
PayrollService  HRService   WorkforceService  ReportService
(motor planilla)(empleados)  (predictivo)     (CCSS/IMSS/DIAN)
   │              │              │              │
   └──────────────┴──────────────┴──────────────┘
                             │
                    ┌────────▼────────┐
                    │  PostgreSQL 16   │
                    │  (pm_* tables)   │
                    └─────────────────┘
                             │
                    ┌────────▼────────┐
                    │  Celery Workers  │
                    │  Redis Streams  │
                    └─────────────────┘
                             │
         ┌───────────────────┼──────────────────┐
         ▼                   ▼                  ▼
   CCSS CR (web       SAT MX (CFDI      DIAN CO (FE
   services CCSS)     nómina IMSS)      electrónica)
         │
    ┌────▼────────────────────────────────┐
    │ Redis Streams (eventos cross-módulo) │
    │  ← StockMind OC_aprobada           │
    │  ← SalesMind pipeline_ganado       │
    │  ← BookMind forecast_actualizado   │
    └─────────────────────────────────────┘

2. API endpoints principales

2.1 Planilla

POST   /api/v1/paymind/planilla/calcular-borrador      # Calcula sin confirmar
GET    /api/v1/paymind/planilla/borrador/{planilla_id} # Ver desglose línea por línea
POST   /api/v1/paymind/planilla/confirmar              # Confirma + genera archivo banco
GET    /api/v1/paymind/planilla/{planilla_id}/archivo-banco  # Descarga ACH/SINPE
GET    /api/v1/paymind/planilla/{planilla_id}/recibos   # Descarga todos los recibos PDF
GET    /api/v1/paymind/planilla/historial               # Planillas procesadas anteriores

POST /api/v1/paymind/planilla/calcular-borrador — request:

{
  "tenant_id": "uuid",
  "empresa_id": "uuid",
  "periodo": {"inicio": "2032-08-01", "fin": "2032-08-15"},
  "pais": "CR",
  "novedades": [
    {"empleado_id": "uuid", "tipo": "hora_extra", "horas": 8},
    {"empleado_id": "uuid", "tipo": "ausencia", "dias": 2, "tipo_ausencia": "incapacidad"}
  ]
}

POST /api/v1/paymind/planilla/calcular-borrador — response:

{
  "planilla_id": "pm-planilla-uuid",
  "estado": "borrador",
  "periodo": {"inicio": "2032-08-01", "fin": "2032-08-15"},
  "resumen": {
    "empleados_procesados": 23,
    "total_salarios_brutos": 8234500.00,
    "total_deducciones_empleados": 879082.00,
    "total_cargas_patronales": 2195490.00,
    "total_a_depositar": 7355418.00,
    "moneda": "CRC"
  },
  "detalle_empleados": [
    {
      "empleado_id": "uuid",
      "nombre": "Ana Ramírez",
      "salario_bruto": 850000.00,
      "deducciones": {
        "ccss_empleado": 90745.00,
        "isr": 32000.00,
        "otros": 0.00
      },
      "salario_neto": 727255.00,
      "cargas_patronales": {
        "ccss_patronal": 226950.00,
        "banco_popular": 8500.00
      }
    }
  ],
  "tabla_regulatoria_version": "CR-2032-v1",
  "generado_en": "2032-08-15T08:30:00Z"
}

2.2 Empleados

GET    /api/v1/paymind/empleados                        # Lista con paginación
POST   /api/v1/paymind/empleados                        # Crear nuevo empleado
GET    /api/v1/paymind/empleados/{id}                   # Expediente completo
PATCH  /api/v1/paymind/empleados/{id}                   # Actualizar datos
GET    /api/v1/paymind/empleados/{id}/vacaciones         # Vacaciones acumuladas
POST   /api/v1/paymind/empleados/{id}/ausencia           # Registrar ausencia
GET    /api/v1/paymind/empleados/{id}/cesantia-proyectada # Cálculo de cesantía

2.3 Workforce planning

GET    /api/v1/paymind/workforce/forecast                # Forecast necesidades 90 días
GET    /api/v1/paymind/workforce/costos                  # Proyección costo planilla 90 días
POST   /api/v1/paymind/workforce/escenario               # Simular contratación manual
GET    /api/v1/paymind/workforce/senales                 # Señales de módulos hermanos

2.4 Reportes regulatorios

GET    /api/v1/paymind/reportes/ccss/{mes}               # Planilla CCSS CR (XML + PDF)
GET    /api/v1/paymind/reportes/imss/{mes}               # SUA MX
GET    /api/v1/paymind/reportes/isr-laboral/{mes}        # ISR retenido CR/MX/CO
POST   /api/v1/paymind/reportes/ccss/{mes}/enviar        # Envío directo a portal CCSS

3. Motor de planilla — servicio de cálculo

class PayrollService:
    async def calcular_planilla(
        self,
        tenant_id: UUID,
        empresa_id: UUID,
        periodo: PeriodoPlanilla,
        pais: str,
        novedades: list[NovedadPlanilla]
    ) -> PlanillaBorrador:
        # 1. Cargar empleados activos del período
        empleados = await self.db.get_empleados_activos(empresa_id, periodo)

        # 2. Cargar tabla regulatoria vigente para el período
        tabla = await self.db.get_tabla_regulatoria(pais, periodo.fin)
        # Tablas versionadas por fecha de vigencia — NUNCA sobreescritas

        # 3. Calcular por empleado (determinístico)
        detalle = []
        for emp in empleados:
            calculo = self.calcular_empleado(emp, tabla, novedades, periodo)
            detalle.append(calculo)

        # 4. Persistir como borrador (no confirmado)
        planilla_id = await self.db.crear_borrador(tenant_id, empresa_id, periodo, detalle)
        return PlanillaBorrador(planilla_id=planilla_id, detalle=detalle)

    def calcular_empleado(
        self,
        empleado: Empleado,
        tabla: TablaRegulatoriaVigente,
        novedades: list[NovedadPlanilla],
        periodo: PeriodoPlanilla
    ) -> CalculoEmpleado:
        dias_trabajados = self._calcular_dias_trabajados(empleado, periodo, novedades)
        salario_periodo = empleado.salario_mensual * (dias_trabajados / 30)

        # Horas extra
        horas_extra_novedad = next((n for n in novedades if n.empleado_id == empleado.id and n.tipo == 'hora_extra'), None)
        valor_hora_extra = (empleado.salario_mensual / 30 / 8) * tabla.factor_hora_extra
        total_horas_extra = (horas_extra_novedad.horas * valor_hora_extra) if horas_extra_novedad else 0

        salario_bruto = salario_periodo + total_horas_extra

        # Deducciones empleado
        ccss_empleado = round(salario_bruto * tabla.tasa_ccss_empleado, 2)
        isr = self._calcular_isr(salario_bruto, tabla.tramos_isr)

        salario_neto = salario_bruto - ccss_empleado - isr

        # Cargas patronales
        ccss_patronal = round(salario_bruto * tabla.tasa_ccss_patronal, 2)

        return CalculoEmpleado(
            empleado_id=empleado.id,
            salario_bruto=salario_bruto,
            ccss_empleado=ccss_empleado,
            isr=isr,
            salario_neto=salario_neto,
            ccss_patronal=ccss_patronal,
            tabla_version=tabla.version  # Auditoría: qué tabla se usó
        )

4. Integración cross-módulo (workforce planning)

PayMind suscribe a eventos de los módulos hermanos para el motor de workforce:

# Consumer de Redis Streams — eventos de otros módulos
async def process_portfolio_event(event: dict):
    match event['type']:
        case 'sm:oc_aprobada':
            # Orden de compra aprobada → más trabajo de bodega esperado
            await workforce_service.actualizar_senal(
                tenant_id=event['tenant_id'],
                tipo='bodega',
                delta=event['payload']['lineas_count'],
                fecha_estimada=event['payload']['fecha_entrega']
            )
        case 'sl:pipeline_ganado':
            # Negocio cerrado en SalesMind → más trabajo de ventas/despacho
            await workforce_service.actualizar_senal(
                tenant_id=event['tenant_id'],
                tipo='ventas',
                delta=event['payload']['monto_usd'],
                fecha_estimada=event['payload']['fecha_cierre']
            )
        case 'bk:forecast_actualizado':
            # Nuevo forecast de BookMind → actualizar presupuesto disponible
            await workforce_service.actualizar_presupuesto_disponible(
                tenant_id=event['tenant_id'],
                saldo_p50_90d=event['payload']['saldo_p50_90d']
            )

5. Integración BookMind GL (asientos automáticos)

Después de confirmar una planilla, PayMind genera automáticamente los asientos en BookMind:

async def generar_asientos_planilla(planilla: PlanillaConfirmada):
    evento = {
        'type': 'pm:planilla_confirmada',
        'tenant_id': str(planilla.tenant_id),
        'payload': {
            'periodo': planilla.periodo.dict(),
            'total_salarios_brutos': float(planilla.total_bruto),
            'total_ccss_patronal': float(planilla.total_ccss_patronal),
            'total_neto_depositar': float(planilla.total_neto),
            'asientos': [
                # Débito: Gasto de planilla (cuenta 5101)
                {'cuenta': '5101', 'tipo': 'D', 'monto': float(planilla.total_bruto)},
                # Débito: Cargas patronales (cuenta 5102)
                {'cuenta': '5102', 'tipo': 'D', 'monto': float(planilla.total_ccss_patronal)},
                # Crédito: Salarios por pagar (cuenta 2201)
                {'cuenta': '2201', 'tipo': 'C', 'monto': float(planilla.total_neto)},
                # Crédito: CCSS por pagar (cuenta 2301)
                {'cuenta': '2301', 'tipo': 'C', 'monto': float(planilla.total_ccss_patronal + planilla.total_ccss_empleado)},
                # Crédito: ISR retenido por pagar (cuenta 2302)
                {'cuenta': '2302', 'tipo': 'C', 'monto': float(planilla.total_isr)},
            ]
        }
    }
    await redis.xadd('bk:asientos_automaticos', evento)

6. Generación de archivos bancarios

class ArchivoBancoService:
    def generar_sinpe_cr(self, planilla: PlanillaConfirmada) -> bytes:
        # Formato SINPE Móvil / IBAN (BCCR especificación técnica)
        lineas = [
            f"01{planilla.empresa.iban_cr}{planilla.total_neto:015.2f}...",  # Cabecera
        ]
        for emp in planilla.detalle:
            lineas.append(
                f"02{emp.empleado.iban}{emp.salario_neto:015.2f}"
                f"{emp.empleado.nombre:<40}"
            )
        lineas.append(f"09{len(planilla.detalle):010d}{planilla.total_neto:015.2f}")  # Totalizador
        return '\r\n'.join(lineas).encode('latin-1')

7. Workers Celery

Worker Tarea Schedule
workforce_worker Recalcular forecast de necesidades de headcount Diario 6:00 AM
regulatory_worker Verificar si hay nuevas tasas publicadas Semanal + evento manual
cesantia_worker Recalcular cesantías acumuladas de todos los empleados Mensual (1er día)
vacation_worker Actualizar días de vacaciones acumuladas Quincenal
gl_asientos_worker Enviar asientos de planilla confirmada a BookMind Post-confirmación (evento)

8. Testing

Capa Cobertura objetivo
Motor de planilla (cálculos regulatorios) ≥99.5% — fixtures oficiales por país
Generación archivo bancario ≥99% — validado contra especificaciones BCCR
Integración cross-módulo (Redis Streams) ≥90%
API endpoints ≥80%
Motor workforce predictivo ≥85%

Obligatorio antes de producción: test end-to-end con 3 empleados reales en sandbox (no datos de producción), verificando que el archivo banco generado es válido para el banco.


Ver también: Doc 06 (SRD Frontend — wizard de planilla) · Doc 08 (Modelo de Datos — schema pm_) · Doc 09 (Motor Core — algoritmos de cálculo) · Doc 10 (Seguridad — cifrado de salarios)*