Pilot es un ERP multiempresa, modular y con API primero, pensado para PYMES de El Salvador. La versión 1.0 incluye el núcleo (usuarios, espacio de trabajo, apps) y una sola app instalable: Contabilidad, que además recibe operaciones de otras apps a través de n8n. La facturación electrónica (DTE) queda para una versión posterior (docs/diferido/).
Contenido de la carpeta entregables/:
| Archivo | Para qué sirve |
|---|---|
entregables/README.md |
Manual completo: instalación, accesos, roles, base de datos, catálogo, historial Git |
entregables/schema.sql |
Esquema completo de la base de datos (tablas, índices, RLS, triggers, permisos) |
entregables/data.sql |
Catálogo de cuentas y demás datos globales (IVA, reglas, apps) |
entregables/historial-git.txt |
Historial Git de todas las ramas |
entregables/guion-demo.md |
Guion de presentación de 10–12 minutos con datos de ejemplo |
entregables/MANUAL-DE-APLICACION-DE-CUENTAS-COMERCIAL .pdf |
Manual de la Universidad Católica en que se basa el catálogo |
| # | Requisito de la rúbrica | Dónde se ve | Cómo se garantiza |
|---|---|---|---|
| 1 | Libro Diario con Fecha, Código/Cuenta, Concepto, Debe y Haber; bloquea el guardado si no cumple la partida doble | Contabilidad → Libro Diario → Nuevo asiento | Se valida tres veces: formulario (Zod + decimal.js, botón Guardar deshabilitado con la diferencia), backend (CON-001 a CON-005, 422 con la diferencia exacta) y un trigger diferido de PostgreSQL que aborta el COMMIT si Σ Debe ≠ Σ Haber o hay menos de 2 líneas. Los asientos guardados no se modifican ni se borran: se corrigen con una reversión |
| 2 | Mayorización automática en tiempo real con saldo Deudor/Acreedor | Contabilidad → Mayor (y Balanza) | Se mayoriza en la misma transacción que guarda el asiento (tabla saldo_cuenta_mensual, ADR-018): o se guardan asiento y saldos, o nada. Saldo = Debe − Haber; positivo = Deudor, negativo = Acreedor. Un diagnóstico verifica que los saldos coincidan con las líneas |
| 3 | Estados financieros automáticos por el primer dígito del código | Contabilidad → Reportes → Estado de Situación Financiera y Estado de Resultados | Clasificación por clase (1 activo, 2 pasivo, 3 patrimonio, 4 costos y gastos, 5 ingresos). Balance: Activo = Pasivo + Patrimonio + utilidad del ejercicio (ADR-016, ADR-037), con alerta y diferencia exacta si no cuadra. Resultados: Ingresos (5) − Costos y gastos (4) = utilidad (el impuesto sobre la renta, grupo 44, se muestra aparte) |
| 4 | Datos complementarios | Reportes: Balanza de Comprobación, Resumen de IVA, Diagnóstico; Libro Diario y Mayor; exportación PDF/XLSX/CSV; bitácora de operaciones de n8n | IVA 13 % leído de la tabla tasa_impuesto con vigencia (nunca fijo en el código); montos siempre con BigDecimal/NUMERIC(19,2); las exportaciones repiten exactamente las cifras de la pantalla |
Además: aislamiento por empresa con Row-Level Security, auditoría de toda mutación, idempotencia (Idempotency-Key) y un webhook para n8n que recibe el cierre de ingresos diarios (docs/, sección 12 de la guía técnica).
┌──────────────────────────┐ HTTPS + OIDC (PKCE) ┌────────────────────┐
│ Frontend React (Vite) │────────────────────────►│ Keycloak (realm │
│ shell + app Contabilidad │◄────────────────────────│ "pilot", MFA TOTP) │
└────────────┬─────────────┘ tokens └─────────▲──────────┘
│ REST /api/v1 (Bearer JWT, X-Empresa-Id) │ valida JWT
┌───────▼───────────────────────────────────────┐ │
│ API Spring Boot (monolito modular hexagonal) │──────────┘
│ plataforma · contabilidad · integracion │◄── n8n (API key, webhook)
└───────┬───────────────────────────────────────┘
│ usuario pilot_app (sin BYPASSRLS)
┌───────▼───────┐
│ PostgreSQL 17 │ RLS forzado por empresa_id, trigger de partida doble
└───────────────┘
| Capa | Tecnología y versión (tomada de pom.xml, package.json y compose.dev.yml) |
|---|---|
| Backend | Java 21, Spring Boot 4.1.1, Spring Modulith 2.1.1, Spring Data JPA (Hibernate), Flyway, Maven (./mvnw) |
| Reportes | OpenHTMLtoPDF 1.1.87 (PDF), Apache POI 5.5.1 (XLSX), CSV |
| Calidad backend | Spotless (palantir-java-format 2.99.0), Checkstyle 14.1.0, SpotBugs, JaCoCo 0.8.15, ArchUnit 1.5.0, Testcontainers |
| Contrato API | OpenAPI 3.1 (api-spec/openapi/pilot-v1.yaml), openapi-generator 7.25.0 |
| Frontend | React 19.3.0, TypeScript 5.9.3, Vite 8.3.1, TanStack Query 5.103.2, React Router 7.18.4, React Hook Form 7.89.0, Zod 4.6.5, decimal.js 10.6.0, Tailwind CSS 4.3.3, oidc-client-ts 3.5.0 |
| Calidad frontend | Vitest 5.0.1, Testing Library, Playwright 1.63.0, ESLint 10.11.0, Prettier 3.9.9, Orval 8.37.0 (cliente API generado) |
| Gestor de paquetes | pnpm 9.15.9, Node ≥ 22.18.0 |
| Infraestructura local | PostgreSQL 17.11 (alpine), Keycloak 26.3.5, n8n 2.14.2, Mailpit v1.31.2, Docker Compose |
- Docker con el plugin
docker compose(los puertos 5432, 8180, 8025, 1025 y 5678 deben estar libres). - Java 21 (JDK). El proyecto trae el wrapper
./mvnw; no hace falta instalar Maven. - Node.js ≥ 22.18 y pnpm 9.15.9 (
corepack enable && corepack prepare pnpm@9.15.9 --activate). - Git.
cp .env.example .envAbra .env y complete con valores propios (el archivo no se sube a Git):
| Variable | Qué poner |
|---|---|
PG_OWNER_PASSWORD |
Contraseña del usuario pilot_owner (dueño del esquema; solo lo usa Flyway) |
PG_APP_PASSWORD |
Contraseña del usuario pilot_app (el que usa la API; sin privilegios de dueño) |
KC_ADMIN_PASSWORD |
Contraseña del administrador de la consola de Keycloak |
N8N_ENCRYPTION_KEY |
Cadena larga aleatoria para cifrar credenciales de n8n |
N8N_PUERTO |
Opcional; cámbiela (p. ej. 5679) si el 5678 está ocupado |
PILOT_OIDC_ISSUER |
Ya viene con http://localhost:8180/realms/pilot; déjela así |
Para el frontend: cp frontend/.env.example frontend/.env.local (ya trae los valores correctos de desarrollo: VITE_OIDC_AUTHORITY=http://localhost:8180/realms/pilot, VITE_OIDC_CLIENT_ID=pilot-web, VITE_API_BASE_URL=http://localhost:8080/api/v1).
Desde la raíz del repositorio:
docker compose --env-file .env -f infra/docker/compose.dev.yml up -d postgres keycloak mailpit
docker compose --env-file .env -f infra/docker/compose.dev.yml ps # esperar a que estén "healthy"La primera vez, PostgreSQL crea el usuario pilot_app (script infra/docker/postgres/init/01-roles.sh) y Keycloak importa el realm pilot (infra/keycloak/). Compose exige todas las variables del .env aunque no arranque n8n. Para levantar también n8n, quite postgres keycloak mailpit del comando.
set -a; source .env; set +a # exporta PG_OWNER_PASSWORD, PG_APP_PASSWORD y PILOT_OIDC_ISSUER
cd backend
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev,apiAl arrancar, Flyway aplica las migraciones V1–V19 con pilot_owner y la API queda en http://localhost:8080/api/v1, conectada como pilot_app.
cd frontend
pnpm install
pnpm dev # http://localhost:5173Las variables VITE_OIDC_AUTHORITY, VITE_OIDC_CLIENT_ID y VITE_API_BASE_URL se leen de frontend/.env.local; si falta alguna, la app muestra un error de configuración.
cd backend && ./mvnw verify # unitarias + integración (Testcontainers, requiere Docker) + arquitectura + calidad
cd frontend && pnpm lint && pnpm test # ESLint, Prettier y Vitest
cd frontend && pnpm e2e # opcional: Playwright, con todo el sistema arriba| Servicio | URL | Nota |
|---|---|---|
| Aplicación (frontend) | http://localhost:5173 | Entrada para usuarios |
| API REST | http://localhost:8080/api/v1 | Contrato en api-spec/openapi/pilot-v1.yaml |
| Keycloak (login y registro) | http://localhost:8180 | Consola de administración en http://localhost:8180/admin, usuario admin y la contraseña de KC_ADMIN_PASSWORD |
| Mailpit (correos de desarrollo) | http://localhost:8025 | Aquí llegan los correos de verificación |
| n8n (opcional) | http://localhost:5678 (o N8N_PUERTO) |
Reenvía cierres de ingresos al webhook |
Cómo obtener acceso. El realm no trae usuarios de prueba: cada persona se registra.
- Abrir http://localhost:5173 → Registrarse: nombre, apellido, correo, teléfono de El Salvador (8 dígitos, se guarda como
+503…), contraseña (mínimo 12 caracteres) y, opcionalmente, "Acepto recibir recomendaciones por correo". No se pide DUI. - Configurar el TOTP (MFA): es obligatorio para todos. Escanear el QR con una app autenticadora y escribir el código de 6 dígitos.
- Verificar el correo: abrir Mailpit (http://localhost:8025) y pulsar el enlace del correo recibido.
- Al primer inicio de sesión, Pilot crea automáticamente la empresa personal del usuario, con el usuario como
admin_empresa. - Ir a Configuración → Apps e instalar Contabilidad: se precargan el catálogo de cuentas, las reglas de contabilización y la configuración de IVA. Después aparece en el lanzador.
Jerarquía: admin_empresa ⊃ contador ⊃ auditor. integracion es un rol técnico independiente (solo API keys). En la versión abierta 1.0, todo usuario es admin_empresa de su espacio personal; los demás roles existen en el modelo y en la API, y su gestión de miembros es de la edición Enterprise.
| Rol | Quién lo usa | Puede | No puede |
|---|---|---|---|
admin_empresa |
Dueño del espacio de trabajo | Todo lo de contador; cambiar el nombre del espacio; crear y revocar API keys; instalar apps |
— |
contador |
Quien lleva la contabilidad | Todo lo de auditor; registrar y revertir asientos (Libro Diario), vista previa, crear y editar cuentas del catálogo, editar configuración y reglas de contabilización, ejecutar el diagnóstico de mayorización |
Gestionar API keys ni apps |
auditor |
Revisión y consulta | Solo lectura: Libro Diario, Mayor, Balanza, Estado de Situación Financiera, Estado de Resultados, Resumen de IVA, catálogo, configuración, reglas, bitácora de n8n y exportaciones PDF/XLSX/CSV | Crear, editar o revertir nada |
integracion |
API key de n8n (alcance integracion:operaciones) |
Solo POST /api/v1/integraciones/n8n/operaciones |
Cualquier otra ruta; no satisface roles de usuario |
Toda petición valida además la membresía a la empresa (X-Empresa-Id), y la base de datos filtra por empresa con RLS.
- Motor: PostgreSQL 17, esquema compartido con columna
empresa_idy Row-Level Security forzado en todas las tablas de negocio. - Roles:
pilot_owner(dueño del esquema; solo Flyway),pilot_app(usuario de la aplicación: sin superusuario niBYPASSRLS; sobreasiento_lineasoloSELECT, INSERT, ningúnDELETE) ypilot_busqueda(sin login; funciones de búsqueda previas a conocer la empresa). - Migraciones Flyway V1–V19 en
backend/src/main/resources/db/migration/: extensiones, idempotencia, auditoría, usuarios, empresas y membresías, apps, API keys, IVA, plantillas contables, catálogo y configuración, libro diario con trigger de partida doble, saldos mensuales, catálogo NIIF, operaciones guiadas, cuentas del sistema, activos fijos y el catálogo de la Universidad Católica (V19). schema.sql: esquema completo (sin el historial de Flyway) con tablas, índices, políticas RLS, trigger de partida doble, permisos (GRANT) y comentarios. No crea los roles: en una base vacía, créelos antes coninfra/docker/postgres/init/01-roles.sh(o ejecute Flyway, que es el camino recomendado).data.sql:INSERTde las tablas globales:plantilla_cuenta(catálogo de cuentas),plantilla_regla_contabilizacion,plantilla_configuracion_contable,tasa_impuesto(IVA 13 %),aplicacionyplantilla_vida_util. El catálogo de cada empresa se copia deplantilla_cuentaal instalar Contabilidad.- Regenerar (con compose arriba y la base ya migrada):
# Con la base ya migrada (backend levantado al menos una vez):
docker compose --env-file .env -f infra/docker/compose.dev.yml exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --schema-only --no-owner -T flyway_schema_history' > entregables/schema.sql
docker compose --env-file .env -f infra/docker/compose.dev.yml exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --data-only --inserts -t plantilla_cuenta -t plantilla_regla_contabilizacion -t plantilla_configuracion_contable -t tasa_impuesto -t aplicacion -t plantilla_vida_util' > entregables/data.sqlEl catálogo base se apoya en el manual de aplicación de cuentas comercial de la Universidad Católica (archivo PDF en esta carpeta) y su decisión de arquitectura es el ADR-044 (docs/adr/, índice en docs/adr/README.md).
- Clases usadas: 1 Activo, 2 Pasivo, 3 Patrimonio, 4 Costos y gastos, 5 Ingresos. El primer dígito del código clasifica la cuenta en los estados financieros.
- Se omitió: los niveles de 10 y 11 dígitos del manual (Pilot llega a 8 dígitos, nivel de detalle) y las clases 6 y 7 (cuentas liquidadoras y de orden, que solo se usan en el cierre anual, fuera del alcance de 1.0).
- Cuentas propias de Pilot agregadas para que el sistema funcione: IVA débito fiscal (
21020101) e IVA crédito fiscal (11040101), que son fijas y no se editan; las cuentas de ventas por tratamiento fiscal (gravadas, exentas y no sujetas), las de cobro por forma de pago que usan las reglas de n8n, y las de activos fijos y depreciación. Los códigos definitivos están endata.sql(plantilla_cuenta). - Las cuentas copiadas del catálogo base son cuentas del sistema (no se editan); cada empresa puede agregar subcuentas propias debajo de ellas.
- La validación del catálogo por un contador sigue pendiente (ver limitaciones).
- Ramas por fase:
feat/f0-fundaciones,docs/f1-decisiones,feat/f2-catalogo-configuracion,feat/f3-libro-diario,feat/f4-f5-reportes-n8nymain(integración). - Convención: Conventional Commits en español, por ejemplo
feat(contabilidad): agrega la exportación de reportes a PDF, XLSX y CSV. Tipos usados:feat,fix,docs,chore,refactor,test. - Cómo leer
historial-git.txt: cada bloque es un commit con hash, ramas y etiquetas (decorate), autor, fecha ISO y los archivos tocados con su número de líneas (--stat); el*y las líneas del grafo muestran cómo se unieron las ramas. Al final hay un resumen de commits por autor. - Regenerar:
{ git shortlog -sn --all; echo; git log --all --graph --decorate --date=iso --stat; } > entregables/historial-git.txtVolumen de pruebas (conteo aproximado por búsqueda en el repositorio, no un resultado de ejecución):
| Nivel | Cantidad |
|---|---|
Backend: clases de prueba unitaria (*Test) |
37 archivos |
Backend: clases de integración (*IT, Testcontainers con PostgreSQL) |
62 archivos |
Backend: métodos de prueba (@Test y parametrizados) |
~535 |
| Frontend: archivos Vitest | 43 |
| Frontend: casos Vitest | ~254 |
| Extremo a extremo (Playwright) | 5 archivos de especificación (F1, F2, F3, F4 y flujo completo) |
Cubren, entre otros: casos dorados de partida doble e IVA (113.00 → 100.00 + 13.00, etc.), aislamiento entre empresas, permisos de inmutabilidad, mayorización, reportes, exportaciones y arquitectura (Spring Modulith y ArchUnit). El estado detallado por fase está en docs/plan-de-trabajo.md.
Limitaciones conocidas de la demo:
- El catálogo de cuentas, las reglas por defecto y el tratamiento del IVA están cargados como borrador, pendientes de validación por un contador (
docs/contabilidad/). - Los estados financieros son estados de gestión (no un juego completo conforme a NIIF para PYMES); no hay cierres contables, flujo de efectivo ni comparativos.
- La depreciación de activos fijos está bloqueada hasta confirmar las vidas útiles con el contador.
- Solo hay una app instalable (Contabilidad); Ventas, Clientes, Proveedores, Inventario y Marketing se muestran bloqueadas como Enterprise, y la facturación electrónica (DTE) no está incluida.
- Configuración de desarrollo: SMTP a Mailpit,
pilot_ownercon privilegios amplios en el contenedor y un solo perfil de ejecución; el despliegue de producción (Traefik, respaldos, observabilidad) queda para una fase posterior. - La versión abierta no tiene miembros adicionales por empresa: agregar usuarios y cambiar roles es de la edición Enterprise.