Files
balam/planeacion/Plan-Etapa1.md
T
JohannVelazquez e057fb9127 Etapa 1: plan de ejecución, corte de avance del 16-jul y fuentes
- 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
2026-07-19 20:05:01 -06:00

14 KiB
Raw Blame History

Plan de ejecución — Etapa 1 · Plataforma base y sincronización con BIND

Proyecto: Plataforma de Automatización Financiera · Balam Ventana: 1324 jul 2026 (semanas 23) · Rango contractual: 3239 h · $19,200$23,400 + IVA Corte de referencia: 16-jul-2026 · 8.00 h registradas → restan ~2431 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, L142157), 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 L148155) 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 (≈ 1415 h desde el 16-jul).

F1 (16-jul) — Fundación de datos

  • B0 · Preparación local (0.51 h): docker-compose.yml con Postgres 17 (puerto 5433 para no chocar); cadena de conexión en appsettings.Development.json; DesignTimeDbContextFactory; referencia Infrastructure → Integrations.Bind. Docker 29.x ya instalado; dotnet-ef 10.0.4 global (invocarlo desde PowerShell — el PATH de git-bash no lo ve).
  • B1 · Modelo de datos + Identity + migración inicial + seed (3.54.5 h):
    • Entidades nuevas en Balam.Domain/Entities/: Tenant, Client (Id = GUID de BIND; NormalizedRfc, NormalizedName, DuplicateOfClientId, SourceHash, DetailSyncedAtUtc), Invoice (modelo central: Uuid null ⇒ prefactura, OpenBalance recalculado en sync, CfdiPaymentTermRaw PPD/PUE del detalle, PaidDetectedAtUtc, LastKnownPlatformStatus), Quote, QuoteInvoiceLink (ADR-003; incluye JiraTicketKey — ADR-006), InvoiceStatusChange (ADR-004), WriteOperation (nace aquí para no re-migrar).
    • Extender SyncCheckpoint y AuditLogEntry con TenantId; índice único (TenantId, Resource).
    • BalamDbContextIdentityDbContext<BalamUser, IdentityRole<Guid>, Guid> (BalamUser vive en Infrastructure; Domain queda puro).
    • Convenciones ANTES de la primera migración: fechas de BIND (sin zona) → timestamp without time zone; timestamps propios *Utctimestamptz; montos decimal(18,2), ExchangeRate (18,6).
    • Migración Inicial + DbSeeder idempotente: tenant Balam (GUID fijo), 4 roles, 5 usuarios ficticios *@balam.dev (password vía Seed:DefaultPassword; jamás datos reales).

F2 (17-jul) — Seguridad, bitácora y primer sync real

  • B2 · Auth + roles + bitácora (45 h): ASP.NET Core Identity + JWT propio (AddIdentityCore + AddJwtBearer; NO MapIdentityApi — 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:SigningKey en user-secrets/Key Vault con fail-fast (mismo patrón que Bind:ApiKey). Sin refresh tokens en E1 (se difieren a Etapa 2 con la UI). Bitácora universal en dos piezas — NO interceptor de SaveChanges (inundaría con los upserts del sync): AuditMiddleware (peticiones mutantes + 401/403) e IAuditLogger explí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.53.5 h): la lista Clients NO tiene campo de fecha filtrable ⇒ full-scan barato de lista cada ciclo (~13 páginas con PaginateAsync) + comparación por SourceHash + detalle solo para nuevos/cambiados (ahí viven CreditDays y contactos). Normalización RFC/nombre (mayúsculas, sin acentos, sin sufijos societarios). Dedup MVP: marcar DuplicateOfClientId por NormalizedRfc exacto 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.55.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 tope Sync: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=1 o saldo ≤ 0.01 con abonos) → Vencida (timbrada, saldo > 0, ExpirationDate < hoy MX) → Vigente (timbrada, en plazo) → Emitida (prefactura: Uuid null). ⚠️ 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 es LastKnownPlatformStatus solo para detectar transiciones.
    • Detección de pagos (ADR-004): transición → fila en InvoiceStatusChanges con fecha de DETECCIÓN (nunca presentarla como fecha valor) + PaidDetectedAtUtc.
    • Cotizaciones: delta por CreationDate con fallback a full-scan (filtro no validado contra producción); partidas Items[] NO se persisten en E1.
    • Moneda: catálogo Currencies (4 filas, 1 GET/ciclo) → CurrencyCode sin 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).

F4 (21-jul) — Robustez

  • B5 · Scheduler + backfill (23 h): BackgroundService + PeriodicTimer cada 15 min (NO Quartz/Hangfire: un solo job, estado en checkpoints, sin dashboard). SemaphoreSlim(1,1) anti-solape; guarda de cuota vía BindQuotaTracker (si Remaining < 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.52.5 h): RLS real de Postgres (compromiso contractual, no basta el filtro EF): migración con ALTER TABLE ... ENABLE/FORCE ROW LEVEL SECURITY + política tenant_isolation con current_setting('app.tenant_id', true)::uuid y WITH CHECK, por tabla de negocio; TenantConnectionInterceptor (set_config al abrir conexión); filtro global EF (ITenantOwned + ICurrentTenantProvider con 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 (34 h): pipeline Draft → DryRun → Confirm persistido en WriteOperations, todo auditado. BindWriteClient : BindClient (hereda RequestAsync protected y sus salvaguardas). Flags BindWrite:Enabled=false y BindWrite:AllowConvertToInvoice=false por default. PPD default; PUE solo con "confirmoPue": true explí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 registra QuoteInvoiceLink. Bind:Mode=Write queda documentado como bloqueado hasta autorización expresa (ADR-001).

F6 (2324 jul) — Cierre

  • B8 · Suite de pruebas de flujos críticos (2.53.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 y timestamptz son Postgres-specific → verificación manual en runbook.
  • B9 · Datos de prueba + runbook (12 h): DevDataSeeder ficticio 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.51 No
B1 Modelo + Identity + migración + seed 3.54.5 No (fundacional)
B2 Auth + roles + bitácora 45 GET /api/audit y middleware → post-demo (1)
B3 Sync clientes + dedup 2.53.5 Dedup solo RFC exacto (0.5)
B4 Sync facturas/cotizaciones + cartera 4.55.5 Equivalente MXN (0.5); quotes full-scan simple (0.5)
B5 Scheduler + backfill 23 Backfill con tope fijo (0.5)
B6 RLS + tenancy 1.52.5 No recortable a cero (contractual)
B7 Escritura controlada (dry-run) 34 Sin modelos BIND provisionales si el 22-jul cambia el alcance (1)
B8 Suite de pruebas 2.53.5 Solo casos redundantes
B9 Datos de prueba + runbook + colchón 12 Seeder mínimo (0.5)
Total 2531 (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)

  1. Scheduler: BackgroundService + PeriodicTimer, no Quartz/Hangfire — un solo job, estado ya persistido en SyncCheckpoints (ADR-002), cero esquema extra.
  2. 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).
  3. Identity + JWT sin refresh tokens en E1; se agregan en Etapa 2 con la UI Angular.
  4. Bitácora: middleware HTTP + IAuditLogger explícito; NO interceptor EF (el primer sync insertaría > 1,000 entradas de ruido).
  5. Pruebas con SQLite in-memory; RLS/timestamptz quedan a verificación manual documentada (candidato a job de integración cuando exista CI el 24-jul).
  6. Trampas resueltas por convención: fechas Npgsql (BIND sin zona), "hoy" con America/Mexico_City, partial class Program para WebApplicationFactory, 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, 2124 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.txt del 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.