- Plan-Etapa1.md: plan detallado por fases/bloques (B0-B9) con horas, decisiones de arquitectura y lo bloqueado hasta el 22-jul - Avance-Etapa1-2026-07-16.md + Excel de avance ordenado por fechas (con script generador y de ordenamiento) - WhatsApp 16-jul: invitación al Git recibida, procedimientos de clientes enviados por correo, Erika pide avance de Etapa 1 - Actualiza PENDIENTES y REGISTRO
14 KiB
Plan de ejecución — Etapa 1 · Plataforma base y sincronización con BIND
Proyecto: Plataforma de Automatización Financiera · Balam
Ventana: 13–24 jul 2026 (semanas 2–3) · Rango contractual: 32–39 h · $19,200–$23,400 + IVA
Corte de referencia: 16-jul-2026 · 8.00 h registradas → restan ~24–31 h
Repo de código: github.com/pedro-balam-itsm/BALAM (clonado en balam-plataforma/)
Documento interno de Johann. NO compartir con Balam ni commitear en el repo del cliente. Fuentes: propuesta v1.2 (§Etapa 1, L142–157), ADR-001…006 (
Hallazgos-y-decisiones-Etapa0.md),bind-api-sandbox/VALIDACION-API.md,Seguimiento-horas.md, REGISTRO #40–#45.
1. Estado al 16-jul — entregables contractuales
Objetivo contractual: "Plataforma funcional que ingiere clientes y facturas de BIND vía API, los normaliza, mantiene su trazabilidad y habilita la capa de escritura controlada."
| # | Entregable (propuesta L148–155) | Estado |
|---|---|---|
| 1 | Backend con autenticación, roles (Finanzas, Dirección, Operaciones, Administración) y bitácora de auditoría universal | ❌ Solo existe la entidad AuditLogEntry |
| 2 | Arquitectura multi-tenant (tenant_id + RLS), sin alta autoservicio |
❌ |
| 3 | Cliente de la API de BIND (reintentos, OData, límites, errores) | ✅ Terminado — C#, 13 pruebas xUnit, salvaguardas del sandbox |
| 4 | Capa de escritura controlada (cotización→factura con dry-run + confirmación + feature flag + bitácora) | ❌ Bloqueada hasta el 22-jul (solo backend; la UI es Etapa 2) |
| 5 | Sincronización programada de clientes (normalización y deduplicación) | ❌ |
| 6 | Sincronización programada de facturas y cotizaciones (estado normalizado, tipo de cambio, paginación) | ❌ |
| 7 | Modelo central de facturas con estados (Emitida, Vigente, Vencida, Pagada, Cancelada) | ❌ |
| 8 | Migraciones versionadas y datos de prueba | ❌ |
Avance parcial no contractual ya hecho: solución .NET 10 compilando (Api/Domain/Infrastructure/Integrations.Bind), BalamDbContext con SyncCheckpoint/AuditLogEntry, endpoints /health y /api/bind/status, frontend Angular 21 + Material con tema de marca, CLAUDE.md del repo con reglas duras y salvaguardas.
2. Calendario e hitos externos
| Fecha | Hito |
|---|---|
| jue 17-jul | Fin de la ventana planeada de backend base + multi-tenant |
| vie 18-jul | Demo semanal (30 min, Pedro + gerencia): meta = login + roles + sync real de BIND en Postgres + endpoint de cartera |
| sem 21-jul | Acceso Azure (Pedro + Noé) — no bloquea trabajo local |
| mié 22-jul | Sesión de definiciones Etapa 1 (Arturo + CEO): alta de cliente, fee, particularidades de envío, alcance Jira→BIND. Agenda en Sesion-Etapa1-2026-07-22.md |
| jue 23-jul 7 pm | Validación del prototipo de Etapa 0 |
| vie 24-jul | CI/CD + gestión de secretos · demo y cierre de etapa |
3. Plan de construcción — fases y bloques
Dependencias: B1 → (B2, B3); B3 → B4; B4 → B5; B1 → B6; B2+B1 → B7; todo → B8/B9. B6 y B7 son paralelizables. Mínimo para la demo del 18-jul: B0 + B1 + B2-núcleo + B3 + B4 (≈ 14–15 h desde el 16-jul).
F1 (16-jul) — Fundación de datos
- B0 · Preparación local (0.5–1 h):
docker-compose.ymlcon Postgres 17 (puerto 5433 para no chocar); cadena de conexión enappsettings.Development.json;DesignTimeDbContextFactory; referencia Infrastructure → Integrations.Bind. Docker 29.x ya instalado;dotnet-ef10.0.4 global (invocarlo desde PowerShell — el PATH de git-bash no lo ve). - B1 · Modelo de datos + Identity + migración inicial + seed (3.5–4.5 h):
- Entidades nuevas en
Balam.Domain/Entities/:Tenant,Client(Id = GUID de BIND;NormalizedRfc,NormalizedName,DuplicateOfClientId,SourceHash,DetailSyncedAtUtc),Invoice(modelo central:Uuidnull ⇒ prefactura,OpenBalancerecalculado en sync,CfdiPaymentTermRawPPD/PUE del detalle,PaidDetectedAtUtc,LastKnownPlatformStatus),Quote,QuoteInvoiceLink(ADR-003; incluyeJiraTicketKey— ADR-006),InvoiceStatusChange(ADR-004),WriteOperation(nace aquí para no re-migrar). - Extender
SyncCheckpointyAuditLogEntryconTenantId; índice único(TenantId, Resource). BalamDbContext→IdentityDbContext<BalamUser, IdentityRole<Guid>, Guid>(BalamUservive en Infrastructure; Domain queda puro).- Convenciones ANTES de la primera migración: fechas de BIND (sin zona) →
timestamp without time zone; timestamps propios*Utc→timestamptz; montosdecimal(18,2),ExchangeRate (18,6). - Migración
Inicial+DbSeederidempotente: tenant Balam (GUID fijo), 4 roles, 5 usuarios ficticios*@balam.dev(password víaSeed:DefaultPassword; jamás datos reales).
- Entidades nuevas en
F2 (17-jul) — Seguridad, bitácora y primer sync real
- B2 · Auth + roles + bitácora (4–5 h): ASP.NET Core Identity + JWT propio (
AddIdentityCore+AddJwtBearer; NOMapIdentityApi— emite tokens opacos, el contrato dice JWT).POST /api/auth/login,GET /api/auth/me. Roles sin acentos como identificador (Finanzas,Direccion,Operaciones,Administracion). Policies nombradas (PuedeVerCartera,PuedeDispararSync,PuedeVerBitacora,PuedeOperarEscritura).Jwt:SigningKeyen user-secrets/Key Vault con fail-fast (mismo patrón queBind:ApiKey). Sin refresh tokens en E1 (se difieren a Etapa 2 con la UI). Bitácora universal en dos piezas — NO interceptor deSaveChanges(inundaría con los upserts del sync):AuditMiddleware(peticiones mutantes + 401/403) eIAuditLoggerexplícito (logins, resumen de cada ciclo de sync, transiciones de estado, TODA operación de escritura).GET /api/audit(Direccion/Administracion). - B3 · Sync de clientes (2.5–3.5 h): la lista
ClientsNO tiene campo de fecha filtrable ⇒ full-scan barato de lista cada ciclo (~1–3 páginas conPaginateAsync) + comparación porSourceHash+ detalle solo para nuevos/cambiados (ahí vivenCreditDaysy contactos). Normalización RFC/nombre (mayúsculas, sin acentos, sin sufijos societarios). Dedup MVP: marcarDuplicateOfClientIdporNormalizedRfcexacto excluyendo RFCs genéricos (XAXX010101000/XEXX010101000); sin fuzzy matching.POST /api/sync/run(manual, para la demo) +GET /api/sync/status.
F3 (18-jul am) — Cierre de demo
- B4 · Sync de facturas/cotizaciones + estados + cartera (4.5–5.5 h):
- Facturas: delta
$filter=Date ge datetime'{cursor}'+ re-barrido de la ventana de facturas abiertas (los pagos caen en facturas viejas que el delta por fecha no atrapa) + detalle (1 GET c/u) SOLO nuevas/cambiadas con topeSync:MaxDetailPerCycle(50). Nunca re-barrer todo el detalle (histórico > 1,000 filas). - Estados de plataforma como función pura con pruebas (
InvoiceStatusMapper): Cancelada (BindStatus=2) → Pagada (BindStatus=1o saldo ≤ 0.01 con abonos) → Vencida (timbrada, saldo > 0,ExpirationDate< hoy MX) → Vigente (timbrada, en plazo) → Emitida (prefactura:Uuidnull). ⚠️ Frontera Emitida/Vigente a confirmar el 22-jul — por eso es función pura, cambiarla cuesta minutos. Vencida/Vigente se computan en la consulta (dependen de "hoy"); lo persistido esLastKnownPlatformStatussolo para detectar transiciones. - Detección de pagos (ADR-004): transición → fila en
InvoiceStatusChangescon fecha de DETECCIÓN (nunca presentarla como fecha valor) +PaidDetectedAtUtc. - Cotizaciones: delta por
CreationDatecon fallback a full-scan (filtro no validado contra producción); partidasItems[]NO se persisten en E1. - Moneda: catálogo
Currencies(4 filas, 1 GET/ciclo) →CurrencyCodesin pedir detalle. - Endpoints:
GET /api/cartera(agregados por moneda — nunca sumar monedas entre sí),GET /api/invoices?status=&clientId=,GET /api/clients?search=(marca duplicados),GET /api/quotes. - El sync inicial del histórico se corre el jueves 17 en la noche — jamás en vivo en la demo (en demo solo el incremental, <10 req).
- Facturas: delta
F4 (21-jul) — Robustez
- B5 · Scheduler + backfill (2–3 h):
BackgroundService+PeriodicTimercada 15 min (NO Quartz/Hangfire: un solo job, estado en checkpoints, sin dashboard).SemaphoreSlim(1,1)anti-solape; guarda de cuota víaBindQuotaTracker(siRemaining < Sync:QuotaReserve= 500, el ciclo se salta y se audita). Backfill gradual del detalle histórico por ciclo. Presupuesto validado: <10 req/ciclo ≈ 1,000 req/día = 5 % de la cuota. Nota Azure: requiere Always On + instancia única. - B6 · RLS + tenancy (1.5–2.5 h): RLS real de Postgres (compromiso contractual, no basta el filtro EF): migración con
ALTER TABLE ... ENABLE/FORCE ROW LEVEL SECURITY+ políticatenant_isolationconcurrent_setting('app.tenant_id', true)::uuidyWITH CHECK, por tabla de negocio;TenantConnectionInterceptor(set_configal abrir conexión); filtro global EF (ITenantOwned+ICurrentTenantProvidercon tenant fijo del seed) como segunda capa. Fuera: CRUD de tenants, resolución por request, RLS sobre tablas Identity.
F5 (22-jul, después de la sesión) — Escritura controlada
- B7 · Capa de escritura controlada (3–4 h): pipeline
Draft → DryRun → Confirmpersistido enWriteOperations, todo auditado.BindWriteClient : BindClient(heredaRequestAsyncprotected y sus salvaguardas). FlagsBindWrite:Enabled=falseyBindWrite:AllowConvertToInvoice=falsepor default. PPD default; PUE solo con"confirmoPue": trueexplícito (un PUE erróneo ya les costó un requerimiento del SAT). CERO llamadas reales a BIND — shapes de los POST provisionales (TODO hasta leer la doc del portal con Pedro); la conversión mock registraQuoteInvoiceLink.Bind:Mode=Writequeda documentado como bloqueado hasta autorización expresa (ADR-001).
F6 (23–24 jul) — Cierre
- B8 · Suite de pruebas de flujos críticos (2.5–3.5 h): nuevo proyecto
tests/Balam.Api.Tests(xUnit +WebApplicationFactory+ SQLite in-memory; NO EF InMemory — no valida FKs/únicos; NO Testcontainers — exige red). Cobertura contractual: matriz de roles 401/403/200, sync (delta/checkpoint/detección de pago), multimoneda (cartera no mezcla), salvaguardas de escritura (flag/modo/bitácora). Costo aceptado: RLS ytimestamptzson Postgres-specific → verificación manual en runbook. - B9 · Datos de prueba + runbook (1–2 h):
DevDataSeederficticio multimoneda/multi-estado (entregable 8 y respaldo de demo si BIND fallara); runbook: compose up → run (migra+siembra) → login → sync en vivo → cartera → bitácora →/api/bind/status.
4. Presupuesto de horas
| Bloque | Horas | ¿Recortable si aprieta? |
|---|---|---|
| B0 Preparación local | 0.5–1 | No |
| B1 Modelo + Identity + migración + seed | 3.5–4.5 | No (fundacional) |
| B2 Auth + roles + bitácora | 4–5 | GET /api/audit y middleware → post-demo (−1) |
| B3 Sync clientes + dedup | 2.5–3.5 | Dedup solo RFC exacto (−0.5) |
| B4 Sync facturas/cotizaciones + cartera | 4.5–5.5 | Equivalente MXN (−0.5); quotes full-scan simple (−0.5) |
| B5 Scheduler + backfill | 2–3 | Backfill con tope fijo (−0.5) |
| B6 RLS + tenancy | 1.5–2.5 | No recortable a cero (contractual) |
| B7 Escritura controlada (dry-run) | 3–4 | Sin modelos BIND provisionales si el 22-jul cambia el alcance (−1) |
| B8 Suite de pruebas | 2.5–3.5 | Solo casos redundantes |
| B9 Datos de prueba + runbook + colchón | 1–2 | Seeder mínimo (−0.5) |
| Total | 25–31 (nominal ~27.5) | Recortes identificados ≈ −5 h |
Primer recorte si el viernes se complica: bitácora consultable y sync de cotizaciones se posponen al lunes 21 — la demo exige login + roles + sync + cartera.
5. Decisiones de arquitectura (registro)
- Scheduler:
BackgroundService+PeriodicTimer, no Quartz/Hangfire — un solo job, estado ya persistido enSyncCheckpoints(ADR-002), cero esquema extra. - RLS en dos capas: políticas reales de Postgres (compromiso "tenant_id + RLS") + filtro global EF como defensa; sin resolución de tenant por request (ADR-005: tenant único).
- Identity + JWT sin refresh tokens en E1; se agregan en Etapa 2 con la UI Angular.
- Bitácora: middleware HTTP +
IAuditLoggerexplícito; NO interceptor EF (el primer sync insertaría > 1,000 entradas de ruido). - Pruebas con SQLite in-memory; RLS/
timestamptzquedan a verificación manual documentada (candidato a job de integración cuando exista CI el 24-jul). - Trampas resueltas por convención: fechas Npgsql (BIND sin zona), "hoy" con
America/Mexico_City,partial class ProgramparaWebApplicationFactory, BD obligatoria con fail-fast.
6. Bloqueado y fuera de alcance
Bloqueado hasta la sesión del 22-jul (no adelantar): escritura real a BIND (ADR-001); reglas de alta de cliente, fee y particularidades de envío; alcance Jira→BIND (ADR-006 — solo se persiste el folio); semántica exacta Emitida/Vigente; mapeo CFDIUse→clave SAT.
Fuera de la Etapa 1: UI (Etapa 2); refresh tokens (E2); recordatorios a clientes (Anexo B); multiempresa (ADR-005); fecha valor de pagos/REP (ADR-004 — solo fecha de detección); aging 30/60/90 y tablero (Etapa 3); conciliación bancaria; CI/CD y Azure (actividades propias, 21–24 jul); envío de PDF (aunque InvoicePdfAsync ya existe).
7. Riesgos y pendientes de gestión
Riesgos técnicos: primera corrida del sync en vivo (mitigado: jueves noche); filtro CreationDate de Quotes sin validar (fallback full-scan desde el día uno); repo visible al cliente (solo datos ficticios, passwords de seed configurables, sin co-autoría en commits).
Gestión que toca la etapa:
- Correo formal a Noé con las salvaguardas (borrador listo en Borradores-seguimiento-tecnico.md).
- 5 preguntas técnicas a Pedro: pagos/REP, tabla
CFDIUse→SAT, shape de/{id}/xml, webhooks vs polling, token del Power BI. - Revisar los procedimientos de carga de facturas por cliente que Erika envió por correo el 16-jul (insumo para particularidades de envío).
- Borrar
bind_token_api.txtdel disco (el token ya vive en user-secrets). - Registrar horas diario en Seguimiento-horas.csv y conciliar con Jira cuando Pedro lo habilite.
- Preparar la sesión del 22-jul con Sesion-Etapa1-2026-07-22.md.