Files
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

129 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](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)`.
- `BalamDbContext``IdentityDbContext<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 `*Utc``timestamptz`; 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](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](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](Sesion-Etapa1-2026-07-22.md).