37 KiB
Arquitectura Técnica — Plataforma Balam
Este documento es la decisión técnica que sostendrá el proyecto. Está pensada para:
- Permitir entregar el MVP en 6 semanas (medio tiempo, ~120 h).
- Escalar sin re-escribir cuando se agreguen nuevos módulos, clientes o cuando se decida comercializar como producto SaaS.
- Ser comprensible por otro desarrollador si hay que sumar gente.
Posicionamiento
La plataforma es una capa de operaciones financieras encima de BIND ERP, no un reemplazo. El sistema de verdad para emisión de CFDI y contabilidad sigue siendo BIND (que ya tiene PAC integrado). Lo que falta y aporta valor:
- Cobranza con reglas (lista blanca, recordatorios automáticos, templates)
- Conciliación bancaria sobre PDFs (Banorte/BBVA/IBC Texas)
- Dashboard consolidado de CxC
- Pagos con link (Stripe)
- Alertas y reportes programados
Visión end-to-end (el horizonte que persigue la arquitectura)
El equipo directivo (CEO/CFO/CTO) describió un ciclo financiero completamente desconectado: nómina, facturación, cobranza, conciliación y contabilidad pasan por manos distintas y cada handoff cuesta dinero. El MVP no resuelve todo el ciclo, pero la arquitectura se diseña para llegar a él sin reescribir:
Fase 2-3 (futuro) MVP (lo que construimos en 6 semanas)
────────────────── ────────────────────────────────────
Jira (horas) ┌─ Cobranza
│ │
▼ │
BUK (nómina) ┌─ BIND (facturación) ─────┼─ Conciliación
│ │ │ (PDF + IA)
▼ │ │
Plataforma ───trigger──>│ ├─ Dashboard
(auto-factura) │ │
└─ BIND (asientos) <────────┴─ Pago con link
(Stripe)
Cómo el MVP prepara la visión completa:
| Capacidad futura | Qué construye el MVP que la habilita |
|---|---|
| Trigger de factura desde nómina (BUK) | Outbox pattern + worker — basta agregar un handler nomina.approved |
| Ingesta de horas desde Jira | Schema multi-fuente para facturas (origen ya tipificado) |
| Generación automática de asiento contable contra BIND | Motor de reportería con mapeo evento → asiento ya existe; falta solo conector BIND |
| Banco en vivo (Belvo / Plaid) | Schema statement_line agnóstico a fuente; pipeline de Claude API es reemplazable sin tocar conciliación |
| Anomalía con IA / agentes | Stream de eventos del outbox alimenta cualquier consumidor IA en Fase 2 |
| Multi-tenant comercial | tenant_id + RLS desde día 1 — solo falta onboarding self-service |
Por eso el MVP, aunque acotado, es estratégico: entrega valor operativo en 6 semanas y abre el camino al ciclo completo sin deuda técnica.
┌─────────────────────────────────────────────┐
│ Plataforma Balam (nueva) │
│ Cobranza · Dashboard · Conciliación · Pago │
└──────┬───────────────┬───────────────┬──────┘
│ import │ export │ webhook
│ (archivo) │ (archivo) │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ BIND │ │ BIND │ │ Stripe │
│ (facturas│ │ (asientos│ │ (pago) │
│ + PAC) │ │ + ERP) │ └──────────┘
└──────────┘ └──────────┘
┌──────────┐
│ Claude │
│ API │
│ (parsing │
│ PDFs) │
└────▲─────┘
│
┌────┴─────┐
│ PDFs de │
│ 3 bancos │
└──────────┘
MVP vs diseño completo
El diseño descrito en este documento es la arquitectura objetivo, calibrada para soportar todo el roadmap (incluyendo Fase 2). El MVP de 6 semanas a medio tiempo construye los cimientos completos pero no implementa todos los módulos:
| Componente | MVP (6 sem · medio tiempo) | Fase 2 |
|---|---|---|
| Auth, RBAC, audit log | ✅ | — |
Multi-tenancy en schema (tenant_id + RLS) |
✅ | onboarding self-service |
| Import de catálogo + facturas desde BIND (archivo) | ✅ | API en vivo |
| Emisión de CFDI desde la plataforma | ❌ (BIND lo hace) | sólo si se valida necesidad |
| Multimoneda visualización | ✅ MXN + USD | EUR |
| Cobranza + recordatorios + lista blanca configurable | ✅ | — |
| Pago con link (Stripe Checkout + webhook) | ✅ | domiciliación / recurrentes |
| Dashboard CxC | ✅ básico | cash-flow 30/60/90 |
| Banca: import de PDFs + parsing con Claude API | ✅ | Belvo / Plaid en vivo |
| Conciliación: match exacto + por alias | ✅ | scoring difuso completo |
| Detección de anomalías | ⚠️ solo duplicados + traspasos | z-score, vendor nuevo, horarios |
| Reporte para asientos contables (Excel para BIND) | ✅ | API en vivo |
| Outbox pattern + eventos | ✅ | — |
| Agentes IA / LLM operativos | ❌ | ✅ |
| Portal cliente / móvil | ❌ | ✅ |
| Integración BUK | ❌ | ✅ cuando exponga API |
Por qué este recorte: la arquitectura modular y event-driven permite agregar Fase 2 sin tocar lo entregado. El MVP demuestra valor con el mínimo viable; las decisiones de qué invertir en Fase 2 se toman con datos de uso real, no a priori.
Constraint operativo: sin sandboxes
BIND y BUK solo cuentan con producción. Esto se mitiga así:
- La plataforma no escribe a BIND en MVP. El flujo es estrictamente: BIND exporta → plataforma importa → plataforma exporta para que el contador suba manualmente. Ninguna operación tuya puede corromper BIND.
- Modo dry-run disponible en cualquier acción con efecto externo (envío de correos, generación de links Stripe), con preview obligatorio antes de confirmar.
- Snapshots automáticos de la base de datos antes de cualquier import masivo desde BIND.
- Audit log registra el actor (humano o sistema), la acción, y el delta. Reversible en <5 min para operaciones internas.
1. Principios rectores
| Principio | Implicación |
|---|---|
| Modular monolito > microservicios | Empezamos con un solo despliegue, módulos con fronteras claras. Microservicios sólo si el volumen lo justifica. |
| Boring tech | PostgreSQL, TypeScript, Node, React. Nada exótico. |
| Eventos internos desde día 1 | El core publica eventos (invoice.issued, payment.received); los módulos reaccionan. Eso permite agregar módulos nuevos sin tocar los existentes. |
| Multi-tenancy desde el esquema | tenant_id en todas las tablas (aunque por ahora exista un solo tenant). Cuando llegue el momento de vender el producto, no hay que rehacer la DB. |
| Audit log universal | Todo cambio en entidades financieras se registra (quién, qué, cuándo, antes/después). Cumple RNF-06 y es vital para fiscal. |
| Idempotencia en integraciones | Webhooks, jobs y endpoints externos siempre con idempotency_key. Evita facturas duplicadas o pagos repetidos. |
| Failure isolation | Un error en conciliación nunca tira la facturación. Workers separados, colas independientes. |
2. Stack recomendado
Backend
- Lenguaje: TypeScript (Node.js 22 LTS)
- Framework: NestJS o Fastify + tRPC
- NestJS si esperamos sumar devs (estructura opinionada, fácil onboarding)
- Fastify + tRPC si seguiré solo (más ligero, mejor DX con frontend)
- Recomendación: NestJS — pensando en futura comercialización + onboarding
- ORM: Prisma (migraciones, tipos, ergonomía)
- Validación: Zod (compartida entre front y back)
- Auth: Better-auth o Auth.js (autohospedado, sin lock-in)
Frontend
- Framework: Next.js 15 (App Router)
- UI: shadcn/ui + Tailwind v4
- Data fetching: TanStack Query
- Tablas / data grids: TanStack Table
- Charts: Recharts o Tremor (Tremor está pensado para dashboards financieros)
- Forms: React Hook Form + Zod
Datos
- Primaria: PostgreSQL 16
- Cache / Queue: Redis 7 (BullMQ para jobs)
- Object storage: S3 / R2 (XML/PDF de facturas, estados de cuenta)
Infra (Azure por preferencia del cliente)
- App + Worker: Azure App Service (Linux, plan B2 inicial)
- Base de datos: Azure Database for PostgreSQL Flexible Server
- Storage: Azure Blob Storage (PDFs bancarios cifrados en reposo)
- Cache / Queue: Azure Cache for Redis (Basic) o Upstash Redis serverless si el costo de Azure Redis no se justifica
- Observabilidad: Sentry (errores) + Application Insights (logs/métricas integrado a Azure) + uptime monitoring externo
- CI/CD: GitHub Actions con deploy a Azure App Service
- Secretos: Azure Key Vault
Calidad
- Tests: Vitest (unit) + Playwright (E2E críticos)
- Lint/Format: Biome (rápido, reemplaza ESLint + Prettier)
- Types: TypeScript strict en todo
- Pre-commit: Lefthook
Integraciones obligatorias en MVP
- Import de archivos BIND (formato CSV/Excel a confirmar en Fase 0)
- Parsing de PDFs bancarios: Claude API (Anthropic) — un pipeline por banco
- Pago con link: Stripe Checkout + Webhook
- Tipo de cambio: API del DOF (Banxico) + cache diario
- Email transaccional: Resend (DX excelente) o Postmark
Integraciones diferidas a Fase 2
- PAC propio (solo si Balam decide emitir CFDI desde la plataforma — hoy lo hace BIND)
- Agregador bancario MX: Belvo o Finerio Connect (reemplaza upload manual de PDFs)
- Banco US: Plaid o scraping autorizado del portal IBC
- BUK API (cuando exista)
- Conector contra BIND vía API (cuando exista)
3. Estructura del repositorio (monorepo)
balam/
├── apps/
│ ├── web/ # Next.js (frontend)
│ ├── api/ # NestJS (backend HTTP)
│ └── worker/ # Procesos en background (BullMQ)
├── packages/
│ ├── db/ # Prisma schema + migraciones + tipos
│ ├── core/ # Lógica de dominio compartida
│ ├── contracts/ # Schemas Zod compartidos front/back
│ ├── integrations/ # Clientes para PAC, bancos, etc.
│ └── ui/ # Componentes shadcn customizados
├── docs/ # ADRs, runbooks, manuales
└── infra/ # IaC mínimo, scripts de deploy
Gestor: pnpm workspaces + Turborepo para caché de builds.
4. Modelo de datos (núcleo)
Diagrama lógico simplificado:
tenant ─┬─ user ──── role
├─ customer ─┬─ invoice ──┬─ invoice_line
│ │ └─ cfdi_xml
│ └─ contact
├─ bank_account ─┬─ bank_statement ── statement_line
│ └─ reconciliation_match
├─ payment ─── payment_allocation (M-N con invoice)
├─ exchange_rate (DOF diario)
├─ accounting_entry ─── entry_line
├─ event (event sourcing ligero / outbox)
├─ audit_log
└─ notification (recordatorios enviados, blacklist)
Reglas duras del modelo:
- Toda tabla financiera tiene:
id,tenant_id,created_at,updated_at,created_by,updated_by. - Montos siempre en decimal(18,4) + columna
currencyISO 4217. Nuncafloat. - Fechas en UTC en DB, presentación en CST/MX.
- Soft delete con
deleted_aten entidades editables; hard delete prohibido en facturas/pagos (cancelación lógica). idempotency_keyúnico por tenant en endpoints de creación masiva.
5. Arquitectura de capas
┌─────────────────────────────────────────────┐
│ Frontend Next.js (Dashboard, Forms) │
└──────────────────┬──────────────────────────┘
│ HTTPS + JWT/Session
┌──────────────────▼──────────────────────────┐
│ API NestJS (REST + tRPC opcional) │
│ ├── Auth & RBAC │
│ ├── Controllers / Resolvers │
│ └── Application Services │
└──────┬─────────────────────┬────────────────┘
│ │
│ publish events │ enqueue jobs
▼ ▼
┌──────────────┐ ┌─────────────────────────┐
│ Event Bus │ │ Worker (BullMQ) │
│ (outbox tbl) │ │ ├── invoice timbrado │
└──────┬───────┘ │ ├── bank sync (cron) │
│ │ ├── reconciliation │
▼ │ ├── reminders (cron) │
┌──────────────┐ │ └── reports │
│ Modules │ └────────┬────────────────┘
│ reaccionan │ │
└──────┬───────┘ │
│ │
▼ ▼
┌─────────────────────────────────────────────┐
│ Domain Services + Repositories (Prisma) │
└──────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ PostgreSQL + Redis + S3 │
└─────────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
┌──────┴──────────────┴──────────────┴────────┐
│ Integraciones externas (clientes) │
│ BIND (archivo) · Claude API · Stripe · DOF · Email │
└─────────────────────────────────────────────┘
6. Patrón Event-Driven (clave para escalar)
Tabla event (outbox pattern):
CREATE TABLE event (
id uuid PRIMARY KEY,
tenant_id uuid NOT NULL,
type text NOT NULL, -- 'invoice.issued', 'payment.received'
payload jsonb NOT NULL,
aggregate_id uuid NOT NULL, -- id de la entidad afectada
occurred_at timestamptz NOT NULL,
processed_at timestamptz,
attempts int DEFAULT 0
);
Flujo: Cuando se emite una factura, en la misma transacción se inserta un evento invoice.issued. Un worker lee la tabla y dispara handlers: enviar email al cliente, generar asiento contable, programar recordatorio de cobro.
Por qué importa: mañana cuando agreguen "notificar a Slack al timbrar", se agrega un handler sin tocar el código de facturación. Ese es el diseño que les va a encantar — no porque lo digan, sino porque verán que pedir cambios es barato.
7. Módulos del sistema (bounded contexts)
7.1 Identity & Access (identity)
- Users, tenants, roles, permissions
- Sesiones, MFA opcional (recomendado para finanzas)
- Audit log
7.2 Catálogo (catalog)
- Customers (con flag
is_strategic,auto_reminder_enabled) - Products/Services
- Tax regimes, payment methods (catálogos SAT)
7.3 Sincronización con BIND (bind-sync)
- Importador de archivo de BIND (clientes, facturas, productos, catálogo de cuentas)
- Dedupe por id externo (UUID del CFDI o id de BIND)
- Visualización multimoneda con snapshot de TC al momento de la factura
- Modo dry-run para previsualizar deltas antes de aplicar
- Sincronización programada o manual
7.4 Cobranza (collections)
- Estados de factura derivados de BIND + pagos locales
- Motor de recordatorios (cron + templates editables)
- Lista blanca: clientes con
auto_reminder_enabled=falsejamás reciben (ACUNTIA + Top 3 — configurable) - Tracking de comunicaciones enviadas
7.5 Pagos (payments)
- Stripe Checkout: generación de link por factura
- Webhook receiver: marca factura como pagada al confirmar
- Soporte tarjeta + SPEI
- Conciliación lista para pagos directos por banco (no via Stripe) en módulo banking
7.6 Banca (banking)
- Upload de PDFs (Banorte/BBVA/IBC) por usuario
- Pipeline de extracción con Claude API + validación de totales
- Almacenamiento de statement_lines con dedupe por hash
- (Fase 2: conexiones a Belvo / Plaid)
7.7 Conciliación (reconciliation)
- Motor de matching con scoring (exacto → alias → cola humana)
- Detección de duplicados y traspasos internos
- (Fase 2: detección avanzada de anomalías, z-score, etc.)
7.8 Reportería (reporting)
- Dashboard tiempo real (CxC, vencidas, próximas a vencer)
- Reportes programables (diario/semanal por email)
- Export para BIND: pagos conciliados + movimientos clasificados en Excel/CSV
7.9 Alertas (notifications)
- Email, en-app
- Configurables por usuario y por evento
8. Seguridad y compliance
Obligatorio desde día 1
- TLS everywhere (Let's Encrypt vía proxy)
- Secrets en variables de entorno cifradas, nunca en el repo
- Cifrado en reposo de PostgreSQL (Railway/Fly lo proveen)
- Cifrado a nivel campo para datos sensibles (credenciales bancarias, tokens de agregadores) usando
pgcrypto - Bcrypt para passwords (Argon2 si Better-auth lo permite)
- Rate limiting en endpoints públicos
- CORS estricto
- Headers de seguridad (helmet)
- Validación Zod en cada entrada
- RBAC granular por módulo y acción
Mexicano-específico
- Emisión y sello digital del CFDI: lo maneja BIND con su PAC integrado; la plataforma no toca el SAT en MVP
- Almacenamiento de XML/PDF de facturas en Blob Storage (referenciado por id de BIND) — por 5 años según SAT
- Bitácora de cancelaciones espejo (motivo + UUID sustituto) sincronizada desde el export de BIND
- Tipos de cambio del DOF para reportería interna
Texas (banco US)
- Si hay operación contable real en Texas, revisar requerimientos con contador (no asumir).
- Para el MVP, tratamos las cuentas US como cuentas bancarias normales en USD.
9. Estrategia de tipo de cambio
- Cache diario en tabla
exchange_ratedesde API de Banxico (DOF FIX) - Al emitir factura USD, se "fija" el TC del día en la factura
- Para conciliación de pagos USD a facturas MXN: regla configurable (TC del día del pago o del día de la factura)
- Pérdidas/ganancias cambiarias se registran como asientos automáticos
10. Pipeline de extracción de PDFs bancarios
Los 3 bancos entregan estados de cuenta como PDF (Banorte, BBVA, IBC Bank Texas). No hay CSV/API disponibles. La estrategia:
Upload PDF ──> Almacenar en Blob ──> Job en cola ──> Pipeline por banco
│
┌───────────────┼───────────────┐
▼ ▼ ▼
pdfplumber/ Claude API Validación
pdftotext extracción (totales,
(texto) estructurada duplicados,
→ JSON formato)
│
▼
statement_line[]
│
▼
hash + dedupe + persist
Decisiones clave:
- Texto primero, modelo después. Se extrae texto con
pdfplumber(Python) opdf-parse(Node) y se pasa el texto plano a Claude. Más barato y rápido que mandar el PDF binario. - Un prompt por banco, versionado. Cada prompt incluye: ejemplos few-shot, schema JSON esperado, instrucciones de manejo de saltos de página y casos borde.
- Validación automática post-extracción:
- Suma de movimientos == saldo final - saldo inicial (con tolerancia mínima)
- Cantidad de movimientos == lo que el resumen del PDF indica
- Si validación falla → flag manual review, no se inserta
- Schema único para
statement_line(independiente de banco):{ fecha, monto, tipo: 'cargo'|'abono', concepto, referencia, saldo_post, hash } - Hash de dedupe:
sha256(banco_id + fecha + monto + concepto). Re-upload del mismo PDF es idempotente. - Costo aproximado: 50 facturas/mes → ~3 PDFs/mes × ~20 páginas ×
1500 tokens/pág × $3/MTok input + $15/MTok output. **$2-8 USD/mes** total. Despreciable. - Privacidad: Anthropic API por default no entrena con datos del cliente (Zero Data Retention disponible bajo enterprise agreement si se requiere). Documentado en supuesto §11 de la propuesta.
- Migration path: cuando en Fase 2 se conecte Belvo, el
statement_lineschema no cambia. Solo cambia la fuente. Mismo motor de conciliación reutilizable.
Por qué Claude API y no parsers hardcoded:
- Los bancos cambian formato sin avisar. Un parser regex se rompe; Claude tolera variaciones.
- IBC Texas tiene formato muy diferente a los bancos MX. Reutilizar prompts es trivial; reutilizar regex no.
- Tiempo de implementación: ~3-4 h por banco con Claude vs. 8-12 h por banco con parser custom + tests.
11. Estrategia de conciliación (corazón del proyecto)
Algoritmo en cascada (mayor a menor confianza):
- Match exacto: monto idéntico + referencia/UUID en concepto + ±3 días → auto-match
- Match alto: monto idéntico + cliente identificable por patrón en concepto + ±7 días → auto-match con flag de revisión
- Match medio: monto en ventana ±2% + cliente identificable → cola de revisión humana
- Sin match: se guarda en
unmatched_statement_line, dashboard de pendientes
Cliente identificable por patrón: usar regex / fuzzy match contra alias del cliente (Banorte puede llegar el pago como "BANORTE SA", "BNTE", etc. — armar tabla customer_alias).
Anomalías detectables sin IA:
- Gasto que excede 2σ del promedio mensual de esa categoría
- Cargo duplicado (mismo monto + mismo concepto + ventana 5 días)
- Cargos en horarios atípicos (madrugada, fines de semana si la empresa no opera)
- Vendor nuevo (no aparece en los últimos 6 meses)
Esto es robusto, explicable y gratis. La IA se agrega en v2 sobre estos cimientos, no como reemplazo.
12. Multi-tenancy: ahora vs después
Ahora (MVP):
tenant_iden todas las tablas- Row-level security en PostgreSQL (RLS) con policies por tenant
- Un solo tenant en producción (Balam)
Después (comercialización):
- Onboarding self-service
- Plan/billing del SaaS
- Aislamiento de archivos por tenant en S3
Costo de incluir tenant_id ahora: <5% del esfuerzo. Costo de agregarlo después: rewrite parcial. Por eso se hace desde día 1.
13. Testing strategy
| Tipo | Cobertura objetivo | Herramienta |
|---|---|---|
| Unit (lógica de dominio: cálculos, reglas) | >90% | Vitest |
| Integration (servicios + DB) | rutas críticas | Vitest + Testcontainers |
| E2E (flujos críticos UI) | 5-8 flujos | Playwright |
| Contract (mocks de PAC, Belvo) | endpoints integrados | MSW |
Flujos E2E obligatorios:
- Crear cliente → emitir factura MXN → timbrar → descargar PDF
- Emitir factura USD a cliente extranjero (sin IVA)
- Importar movimiento bancario → conciliar manualmente → ver pago aplicado
- Conciliación automática end-to-end con datos sintéticos
- Enviar recordatorio que respeta lista blanca (cliente Top 3 no recibe)
14. Plan de despliegue
main→ producción (deploy automático tras CI verde + aprobación manual)develop→ staging (deploy automático)- Feature branches → preview environments (Railway lo soporta)
- Rollback en <2 min vía Railway dashboard
Migraciones:
- Siempre backward-compatible (expand → migrate → contract)
- Backup automático antes de cada migración productiva
- Migraciones revisadas manualmente, nunca destructivas en hot-path
15. Observabilidad mínima
- Sentry: errores no manejados, performance de queries lentas
- Better Stack / Axiom: logs estructurados (cada request, cada job)
- Métricas: dashboard interno con (a) facturas/día, (b) pagos conciliados auto vs manual, (c) jobs fallidos, (d) latencia p95
- Alertas a tu correo/Slack:
- Job de timbrado falla
- Sync bancario sin éxito por >4 hrs
-
10 errores en 5 min
- Disco/memoria al 80%
16. ADRs (Architecture Decision Records)
Llevar docs/adr/NNNN-titulo.md con decisiones grandes:
- ADR-0001: Monorepo con pnpm + Turborepo
- ADR-0002: NestJS como framework backend
- ADR-0003: PostgreSQL como única base de datos primaria
- ADR-0004: Multi-tenancy por
tenant_iddesde día 1 - ADR-0005: BIND como fuente de verdad de facturación; plataforma como capa de operaciones
- ADR-0006: Claude API para extracción estructurada de PDFs bancarios (vs parsers hardcoded)
- ADR-0007: Stripe Checkout para pago con link (vs Conekta / MercadoPago)
- ADR-0008: Azure App Service + PostgreSQL Flexible para hosting (vs Railway/Fly)
- ADR-0009: Outbox pattern para eventos internos
Cada ADR: contexto, decisión, alternativas consideradas, consecuencias. Esto vale oro cuando entra el próximo dev (o tú dentro de 6 meses).
17. Roadmap de evolución (post-MVP)
Priorizado por acercarse al ciclo end-to-end que el equipo directivo describió en la llamada original (Jira → BUK → Factura → Cobranza → Conciliación → Asiento):
| Cuándo | Qué | Por qué |
|---|---|---|
| Mes 3-4 | Belvo (MX) en vivo + asientos contables auto contra BIND | Elimina dos cuellos de botella manuales que sobrevivieron al MVP |
| Mes 4-5 | Ingesta de horas Jira → input de facturación + EUR | Habilita el caso de uso #1 del PRD inicial (factura auto desde horas) |
| Mes 5-7 | Integración profunda con BUK (cuando exponga API) — trigger nómina → factura | Cierra el extremo izquierdo del ciclo |
| Mes 7-9 | Domiciliación Stripe, anomalía con IA, portal cliente | Capa de valor agregado sobre el ciclo ya cerrado |
| Mes 9-12 | Multi-tenancy comercial, onboarding self-service, billing del SaaS | Pivote a producto comercializable |
| Año 2 | Marketplace de integraciones (otros PACs, bancos, ERPs) | Crecimiento del producto |
18. Workflow de desarrollo con Claude Code
El proyecto se desarrolla con Claude Code como acelerador. La arquitectura, convenciones y herramientas elegidas en este documento están deliberadamente alineadas con prácticas que maximizan la calidad del output asistido por IA: tipos estrictos, módulos con fronteras claras, ADRs versionados, tests como contrato.
17.1 Estructura de soporte a la IA (en el repo)
.claude/
├── settings.json # permisos de herramientas, hooks
├── skills/ # workflows reutilizables (slash commands)
│ ├── new-module/ # scaffolding de un módulo nuevo
│ ├── new-integration/ # cliente de API externa con tests + retries
│ ├── new-event-handler/ # handler con outbox + tests + idempotencia
│ ├── new-cfdi-test/ # E2E de facturación
│ └── review-pr/ # checklist de revisión antes de merge
├── agents/ # sub-agentes especializados (opcional)
└── memory/ # contexto persistente (decisiones, gotchas)
CLAUDE.md # contexto raíz del proyecto (siempre cargado)
docs/
├── adr/ # decisiones arquitectónicas
├── conventions.md # cómo se nombran cosas, patrones obligatorios
├── domain-glossary.md # vocabulario fiscal/financiero (CFDI, asiento, etc.)
└── integrations/ # docs por integración (Belvo, PAC, BIND)
17.2 CLAUDE.md raíz (esqueleto)
Este archivo se carga automáticamente en cada sesión. Tiene que ser corto y duro:
# Balam — Plataforma de Automatización Financiera
## Contexto crítico
Sistema financiero productivo de empresa real. Errores cuestan dinero,
relaciones con clientes y compliance fiscal. Cuidado extremo con:
- Cálculos de impuestos (decimal(18,4), nunca float)
- Idempotencia en endpoints de creación
- Lista blanca de cobranza (ACUNTIA + Top 3 jamás reciben recordatorio auto)
- Cancelación de facturas: lógica, nunca hard delete
- Multi-tenancy: tenant_id en cada query (RLS activo)
## Reglas duras
- TypeScript strict siempre. Nada de `any`.
- Toda mutación financiera escribe a audit_log en la misma transacción.
- Toda integración externa con retry + idempotency_key.
- Tests obligatorios para: cálculo de impuestos, matching de conciliación,
reglas de cobranza, generación de asientos.
- Nunca commitees secretos. Nunca pegues datos reales de Balam en prompts.
## Stack
NestJS + Prisma + PostgreSQL + Redis (BullMQ) + Next.js + shadcn/ui.
Monorepo pnpm + Turborepo. Tests con Vitest + Playwright. Lint con Biome.
## Comandos comunes
- `pnpm dev` — levanta web + api + worker
- `pnpm test` — corre toda la suite
- `pnpm db:migrate` — aplica migración (siempre backward-compatible)
- `pnpm db:seed` — datos sintéticos para desarrollo
- `pnpm typecheck` — valida tipos sin compilar
## Antes de cerrar una tarea
1. `pnpm typecheck && pnpm test && pnpm lint` debe pasar
2. Si tocaste schema, hay migración + rollback verificado
3. Si tocaste lógica financiera, hay test que la cubre
4. Si tocaste integración externa, hay mock + test contra mock
5. PR description explica el "por qué", no solo el "qué"
## Documentos vivos
- Convenciones: docs/conventions.md
- Glosario fiscal: docs/domain-glossary.md
- ADRs: docs/adr/
- Integraciones: docs/integrations/<nombre>.md
17.3 Slash commands / skills críticos
Cada uno automatiza un workflow repetitivo y enforce las convenciones del proyecto:
| Skill | Qué hace |
|---|---|
/new-module <nombre> |
Crea estructura del bounded context (controller, service, repository, schema Prisma, tests, evento outbox) siguiendo el template del proyecto |
/new-integration <api> |
Genera cliente con reintentos exponenciales, idempotency, error mapping, mock para tests, y registro en docs/integrations/ |
/new-event-handler <evento> |
Crea handler que lee outbox, es idempotente, registra en audit, y tiene test de retry |
/new-cfdi-scenario |
Genera test E2E de Playwright para flujo de facturación específico |
/pre-merge-review |
Corre checklist: types + tests + lint + ADR si aplica + migration safety + no secretos |
/add-adr <titulo> |
Crea nuevo ADR con template estándar y lo lista en MEMORY |
17.4 Sub-agentes (uso disciplinado)
Útiles para tareas paralelizables o que ensucian el contexto principal:
explore— buscar dónde se usa una entidad antes de refactorizarreviewer— segunda lectura sobre cambios sensibles (lógica fiscal, seguridad, queries con tenant_id)integration-debugger— investigar fallos contra mocks de APIs externas sin saturar contexto principal
No abuses. Cada subagente cuesta tokens y tiempo de orquestación.
17.5 Convenciones que multiplican la productividad de la IA
Todas viven en docs/conventions.md:
- Nombres explícitos:
calculateInvoiceTaxesMXN()mejor quecalc(). La IA infiere intención del nombre. - Schemas Zod compartidos en
packages/contracts/: una fuente de verdad para tipos front/back. - Tests como spec: cada función pública con test de happy path + edge cases. La IA usa los tests como contrato vivo.
- Comentarios solo para "por qué", nunca para "qué". Los nombres dicen el qué.
- Archivos cortos: máximo ~300 líneas. Si un archivo crece más, se divide. Mejor para IA, mejor para humanos.
- Errores tipados (
Result<T, DomainError>o excepciones tipadas), nuncathrow new Error('algo')genérico. - Una responsabilidad por archivo. Un servicio, un controller, un schema por archivo.
- README por package con: propósito, entrypoints, ejemplos de uso.
17.6 Seguridad operativa con IA
Reglas no negociables:
- Datos reales de Balam jamás entran a prompts. Para pruebas se usan datos sintéticos generados con scripts (faker + patrones reales).
- Credenciales (PAC, Belvo, Plaid, DB) viven en gestor de secretos. Nunca pegadas en chat ni en
.envversionado. - XMLs de CFDI reales, estados de cuenta, listas de clientes: no van a IA. Para debugging se trabaja sobre versiones anonimizadas.
- Hooks de pre-commit que bloquean: secretos detectados (gitleaks), archivos con extensión
.cfdio.statement, datos en formato BIND real. - Permisos de Claude Code restrictivos (
.claude/settings.json): allowlist explícito para Bash; deny en comandos destructivos sin confirmación; no acceso a/Users/johann/Desktop/Proyectos personales/BALAM/datos-reales/si esa carpeta llega a existir. - Code review pre-merge obligatorio (humano, mío): la IA propone, yo apruebo. Documentado en commit trail.
17.7 Ventaja en velocidad (qué pasa con el presupuesto)
Tareas que se aceleran 40-60% con Claude Code:
- Boilerplate de módulos, controllers, schemas
- Generación de tests (sobre todo edge cases que un humano olvidaría)
- Refactor mecánico (renombrar, mover, extraer)
- Migraciones de schema con rollback
- Documentación (ADRs, READMEs, manuales de usuario)
- Mappers y transformaciones de DTOs
- Mocks de APIs externas
Tareas que NO se aceleran significativamente (sigue siendo trabajo humano):
- Decisiones de arquitectura
- Discovery y conversaciones con stakeholders
- Debugging de integraciones reales (BIND, bancos)
- Validación con datos reales
- Diseño de UX
- Negociación de cambios de scope
Por eso los rangos de horas siguen incluyendo holgura: Fase 0 y debugging de integraciones no se aceleran; lo que se acelera es el 60% restante del trabajo. Espera caer cerca del extremo bajo de cada rango.
17.8 Memoria persistente del proyecto (en .claude/memory/)
Llevar memorias semánticas pequeñas con [[links]] entre ellas. Ejemplos útiles para este proyecto:
gotcha-belvo-paginacion.md— Belvo pagina por cursor, no por offset (descubierto durante Fase 3)gotcha-cfdi-cancelacion.md— cancelación requiere UUID sustituto si motivo=01 (no para motivos 02-04)gotcha-fxrate-dof.md— el DOF publica TC del día siguiente a las 18:00 hrs; usar T-1 para facturas mañanerasconvention-tenant-id.md— toda query nueva debe filtrar por tenant_id explícitamente o usar RLS, jamás confiar en aplicacióndecision-bind-as-source-of-truth.md— BIND queda como fuente de verdad para facturación y contabilidad; plataforma es capa de operaciones (ver ADR-0005)
Estas memorias hacen que la IA no repita errores y que recuerdes tú mismo las decisiones meses después.
19. Por qué este diseño les va a gustar (sin que lo digan)
- Pedir cambios será barato — al ser event-driven, sumar comportamiento es un handler, no una cirugía.
- Auditable — cuando finanzas pregunte "¿quién canceló esa factura?", hay respuesta exacta en 5 segundos.
- Sin lock-in — el código es suyo, el hosting es portable, sin SDKs propietarios.
- Multi-tenant ready — el día que decidan vender el producto, no hay rewrite.
- Explicable — cualquier dev puede leer el monorepo y entender qué hace cada módulo en una tarde.
- Robusto — el motor de conciliación funciona sin IA; la IA es mejora, no dependencia.
- Respeta lo que ya funciona — BIND queda como sistema de verdad para facturación y contabilidad. La plataforma orquesta, no reemplaza. Esto reduce riesgo, costo y resistencia al cambio.
- Construido con toolchain moderno — desarrollo asistido por IA con revisión humana, tests exhaustivos y documentación al día. Mismo resultado, menos horas facturadas, mejor mantenibilidad.