MATI — Compliance laboral y tributario
Plataforma para despachos contables salvadoreños que administran carteras de PyMEs: calendario de obligaciones fiscales con semáforo de vencimientos, cálculos laborales deterministas (indemnización, vacaciones, aguinaldo, ISSS, AFP), libros de IVA con importación desde Excel y CSV, generación de documentos legales y un agente conversacional que responde citando el Código de Trabajo. En producción sobre VPS propio.
NestJS · Next.js · TypeScript · Prisma · PostgreSQL · pgvector · RAG · BullMQ · Clerk · Turborepo

La profesión contable salvadoreña está en transición: despachos con décadas de oficio conviven con una generación que llega esperando otras herramientas. Mati se diseñó para ambos. Automatiza el trabajo repetitivo, deja las decisiones en manos del contador y pone el conocimiento legal —hoy concentrado en la experiencia de unos pocos— al alcance de quien recién empieza.
El problema
Un despacho contable salvadoreño lleva las obligaciones de decenas de PyMEs a mano, entre hojas de cálculo y memoria. Cada vencimiento perdido es una multa que paga el despacho o su cliente, y el trabajo que rodea esas fechas —liquidaciones, libros de IVA, documentos, recordatorios— es repetitivo y fácil de equivocar. Las herramientas contables del mercado registran lo que ya pasó; ninguna avisa de lo que está por vencer ni calcula una indemnización según el Código de Trabajo.
Mi rol
Proyecto propio dentro de Yultic, construido de punta a punta: arquitectura, backend en NestJS, el agente de IA con su RAG legal, el frontend en Next.js y el despliegue en VPS.
La solución y las decisiones
- Multi-inquilino real: la organización de Clerk ES el inquilino. Un guard global exige el identificador de organización en toda petición y cada lectura filtra por despacho. No hay fallback al usuario individual — sin organización, la petición se rechaza en la puerta en lugar de filtrar datos de otro despacho.
- El código manda, no el modelo: el LLM interpreta la pregunta y explica la respuesta, pero los montos salen de funciones deterministas y la ley sale del RAG. El agente nunca inventa una cifra, porque no es él quien la calcula.
- Toda escritura pasa por confirmación humana: el agente propone una acción pendiente y una persona la aprueba. Que pregunte antes de actuar es el diseño, no una limitación.
- Semáforo de vencimientos como función central del producto — vencido, vence pronto, al día —, alimentado por trabajos en segundo plano que generan las alertas de forma idempotente para que un reintento no duplique avisos.
- Aritmética monetaria en decimal exacto, nunca en punto flotante, con un único redondeo al final. En cálculos legales un centavo de diferencia es una liquidación mal hecha.
- Los límites de cada plan se aplican en el backend en tres formas —capacidad, funcionalidad y consumo— y responden con un error estructurado que el frontend convierte en el diálogo de mejora de plan. Las cuentas sin tope se definen por variable de entorno y no en la base de datos, para que no exista ninguna ruta dentro de la app que permita auto-promoverse.
Resultado e impacto
- Desplegado y corriendo en producción en matisv.com, autohospedado en VPS propio con contenedores podman: API, frontend, PostgreSQL con pgvector y Redis.
- 364 tests automatizados en la API, concentrados en los cálculos laborales y tributarios, que son la parte donde un error se convierte en un problema legal para el cliente.
- Auditoría previa al despliegue completada en seis etapas, de las cuales quedan abiertas observabilidad y pruebas end-to-end.
- Sin usuarios todavía: está en estabilización, cerrando los últimos defectos antes de abrirlo a los primeros despachos.
Cómo funciona por dentro
Cuatro decisiones explican el resto del sistema. Todas responden a la misma pregunta: qué pasa cuando el software se equivoca en algo que tiene consecuencias legales.
El RAG legal: la ley no se memoriza, se cita
El Código de Trabajo salvadoreño está indexado en PostgreSQL con pgvector, usando embeddings de voyage-law-2, un modelo especializado en texto jurídico. Cuando el agente necesita fundamento legal llama a una herramienta de búsqueda que devuelve los artículos pertinentes, y responde sobre ese texto recuperado. La diferencia es responsabilidad: un modelo que recita de memoria puede alucinar un artículo que no existe; uno que cita lo recuperado se puede verificar contra la fuente.
Catorce herramientas y ningún número inventado
El agente expone catorce herramientas divididas en laborales y tributarias. El modelo decide cuál llamar y con qué argumentos, pero el cálculo ocurre en código TypeScript probado con tests. El proveedor es OpenRouter, elegido por costo y porque permite cambiar de modelo sin tocar una línea: el comportamiento correcto no puede depender de qué modelo esté detrás.
El centavo que faltaba
Los cálculos de «N días de salario» parecen triviales hasta que se ejecutan en decimal. Dividir primero produce un periódico que al truncarse deja el monto un centavo corto — afecta alrededor del 17% de los salarios, justo cuando los días son múltiplo de tres. El orden de las operaciones es parte de la corrección legal, no una preferencia de estilo.
✗ (salario ÷ 30) × días → 866.6666… se trunca → falta 1 centavo ✓ (salario × días) ÷ 30 → exacto, con un único redondeo half-up al final
Fechas: un día antes es un aviso inútil
El Salvador es UTC−6 y la base guarda en UTC. Leer una fecha almacenada con los getters locales de JavaScript la corre al día anterior, y eso ya hizo que las alertas de aniversario y de vencimiento de contrato salieran veinticuatro horas antes de tiempo. En un producto cuyo valor es avisar en la fecha correcta, la zona horaria no es un detalle de implementación.