From e057fb9127f010a8490137aa045d0e49c1991e71 Mon Sep 17 00:00:00 2001 From: JohannVelazquez Date: Sun, 19 Jul 2026 20:05:01 -0600 Subject: [PATCH] =?UTF-8?q?Etapa=201:=20plan=20de=20ejecuci=C3=B3n,=20cort?= =?UTF-8?q?e=20de=20avance=20del=2016-jul=20y=20fuentes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- bitacora/PENDIENTES.md | 9 +- bitacora/REGISTRO.md | 18 +++ ...cion Git procedimientos y avance Etapa1.md | 57 ++++++++ planeacion/Avance-Etapa1-2026-07-16.md | 50 +++++++ planeacion/Plan-Etapa1.md | 128 ++++++++++++++++++ ...lan-actividades-avance-2026-07-16.bak.xlsx | Bin 0 -> 8306 bytes .../Plan-actividades-avance-2026-07-16.xlsx | Bin 0 -> 11216 bytes planeacion/Plan-actividades.md | 6 +- planeacion/generar_avance_2026_07_16.py | 98 ++++++++++++++ planeacion/ordenar_fechas_avance.py | 113 ++++++++++++++++ plans/etapa1-plan.md | 72 ++++++++++ 11 files changed, 546 insertions(+), 5 deletions(-) create mode 100644 fuentes/2026-07-16 - WhatsApp - Erika invitacion Git procedimientos y avance Etapa1.md create mode 100644 planeacion/Avance-Etapa1-2026-07-16.md create mode 100644 planeacion/Plan-Etapa1.md create mode 100644 planeacion/Plan-actividades-avance-2026-07-16.bak.xlsx create mode 100644 planeacion/Plan-actividades-avance-2026-07-16.xlsx create mode 100644 planeacion/generar_avance_2026_07_16.py create mode 100644 planeacion/ordenar_fechas_avance.py create mode 100644 plans/etapa1-plan.md diff --git a/bitacora/PENDIENTES.md b/bitacora/PENDIENTES.md index 7371063..8bae81f 100644 --- a/bitacora/PENDIENTES.md +++ b/bitacora/PENDIENTES.md @@ -2,7 +2,7 @@ Acciones vivas del proyecto. Formato: `[ ]` abierta · `[x]` cerrada (no se borran, dejan rastro). Cada una con responsable y, si aplica, fecha. Ver contexto en [REGISTRO.md](REGISTRO.md). -_Última actualización: 2026-07-15 (prototipo entregado y con validación agendada para el 23-jul; sesión de reglas de Etapa 1 el 22-jul; repo GitHub autorizado y pendiente de invitación; control local de horas iniciado; factura de 30 h aún pendiente de confirmación)._ +_Última actualización: 2026-07-16 (prototipo con validación agendada para el 23-jul; sesión de reglas de Etapa 1 el 22-jul; invitación al repo GitHub recibida y acceso de escritura por validar; procedimientos por cliente recibidos por correo y pendientes de revisión; avance de Etapa 1 solicitado por Erika)._ ## 🔴 Johann (proveedor) — inmediato @@ -13,6 +13,9 @@ _Última actualización: 2026-07-15 (prototipo entregado y con validación agend - [x] ~~📊 **Responder a Erika el % de avance de Fase 0**~~ → **Atendido mediante el Excel de avance ajustado y entrega del prototipo.** La validación final sigue abierta. - [ ] 📅 **Preparar y asistir a la sesión de reglas/dudas de Etapa 1 (mié 22-jul)** con Arturo + CEO: alta de cliente nuevo, fee en BIND, particularidades de envío, estatus disparador de Jira y confirmación de alcance Jira→BIND. Ver [REGISTRO #42](REGISTRO.md). - [ ] 📅 **Preparar y asistir a la validación del prototipo (jue 23-jul, 7:00 pm).** Llevar decisiones que requieren visto bueno y separar MVP de Anexo B. Ver [REGISTRO #42](REGISTRO.md). +- [ ] 📊 **Compartir con Erika el corte de avance de la Etapa 1 solicitado el 16-jul.** Usar cifras verificables: 8 h registradas de 32–39 h; cliente BIND terminado con 13/13 pruebas; scaffold base parcial; base de datos, autenticación/roles, sincronización y cartera en construcción. Ver [REGISTRO #46](REGISTRO.md). +- [ ] 📄 **Revisar los procedimientos de carga de facturas por cliente enviados por correo el 16-jul.** Conservar correo/adjuntos como evidencia y contrastar destinatarios, adjuntos, asunto, cuerpo y nomenclatura contra el Discovery y el prototipo. Ver [REGISTRO #46](REGISTRO.md). +- [ ] 🔐 **Validar permiso de escritura en el repositorio privado de Balam.** Johann confirmó que puede acceder, pero falta comprobar `clone/push` o el nivel de permiso antes de mover el trabajo local. Ver [REGISTRO #46](REGISTRO.md). - [ ] ⏱️ **Validar el corte reconstruido de 30 h** en `../planeacion/Seguimiento-horas.csv`: 22 h de Etapa 0 + 8 h de Etapa 1. Solo 3.08 h corresponden a sesiones con duración comprobable; las otras 26.92 h están marcadas como reconstruidas. Ajustarlas si la memoria/evidencia indica otra distribución y después conciliar con Jira. Ver [REGISTRO #45](REGISTRO.md). - [ ] 📝 **Cerrar el documento de hallazgos + ADRs** (`../planeacion/Hallazgos-y-decisiones-Etapa0.md`) después de las sesiones del 22–23 jul. - [x] ~~📅 **Proponer a Erika usar la sesión del martes 7-jul para COBRANZA**~~ → **Propuesto (6-jul, 3:01 pm) y CONFIRMADO (6-jul, 6:36 pm):** convocatoria de Teams enviada para el martes 7-jul, 7:00–8:00 am, con Araceli y Arturo (CC Noé). Ver [REGISTRO #34](REGISTRO.md). @@ -52,14 +55,14 @@ _Última actualización: 2026-07-15 (prototipo entregado y con validación agend ## 🟡 Balam - [x] ~~**Balam:** enviar el documento/contrato de firma~~ → **Hecho (25-jun):** contrato enviado vía Paola (RH); ajustado y **firmado el 26-jun**. Ver [REGISTRO #18](REGISTRO.md), [#19](REGISTRO.md). -- [ ] 🔑 **Balam: completar accesos de Etapa 1** — token BIND ✅; manual de marca ✅; repo GitHub autorizado (falta invitación y validar escritura); Azure programado para semana del 21-jul. Ya no bloquea el Discovery, pero sí despliegue/CI-CD. +- [ ] 🔑 **Balam: completar accesos de Etapa 1** — token BIND ✅; manual de marca ✅; invitación al repo GitHub recibida y acceso confirmado informalmente (falta validar escritura); Azure programado para semana del 21-jul. Ya no bloquea el desarrollo local, pero Azure sí condiciona despliegue/CI-CD. - [x] ~~Erika (PM): pedir el plan de actividades~~ → **Recibido (30-jun).** Erika es la **intermediaria de todas las sesiones**, agendó el **kickoff (1-jul, 7am)** y monta el **tablero Kanban en Jira**. Ver [REGISTRO #21](REGISTRO.md). - [x] ~~**Noe:** formalizar por correo~~ → **Hecho:** aclaraciones (8-jun, [#15](REGISTRO.md)) y **luz verde + redacción del documento de firma** (16-jun, [#17](REGISTRO.md)). - [ ] **Pedro + Erika:** armar el **tablero de seguimiento en Jira** y revisarlo juntos (instruido formalmente por Noe el 16-jun). - [x] ~~**Pedro:** enviar **manual de marca**~~ → **Hecho (1-jul):** enviado por correo. Tokens en [`../marca/Marca-Balam.md`](../marca/Marca-Balam.md). Ver [REGISTRO #23](REGISTRO.md). - [x] ~~**Erika:** entregar el **token de BIND**~~ → ✅ **ENTREGADO por correo (6-jul, 12:40 pm)**, mismo día en que se solicitó. Ver [REGISTRO #28](REGISTRO.md). - [x] ~~**Balam:** confirmar **a qué usuario pertenece el token**~~ → ✅ **Confirmado (6-jul, 12:38 pm):** Pedro lo entregó por correo (`bind_token_api.txt`) indicando que fue generado con el **usuario de Arturo Rosas** — conforme al kickoff (Arturo=dev, Ara=Power BI). Ver [REGISTRO #29](REGISTRO.md). -- [ ] **Pedro/Balam:** completar la creación del **repositorio GitHub privado** y agregar a `Johann-28` con permisos de escritura. Noé ya autorizó la creación el 15-jul. Ver [REGISTRO #43](REGISTRO.md). +- [ ] **Pedro/Balam:** confirmar permisos de **escritura** para `Johann-28` en el repositorio GitHub privado. Pedro envió la invitación el 15-jul y Johann confirmó acceso el 16-jul; falta una prueba efectiva de escritura. Ver [REGISTRO #43](REGISTRO.md), [#46](REGISTRO.md). - [ ] **Pedro:** enseñar a Johann el **flujo de Jira** (reporte de horas como los demás consultores). - [ ] **Pedro + Noé:** **configurar Azure** y permisos (Noé otorga donde Pedro tiene limitantes). - [x] ~~**Erika:** **coordinar la sesión de Discovery con Arturo + Araceli**~~ → **Hecho:** agendada (1-jul) y **realizada (6-jul, 7am)**. Ver [REGISTRO #24](REGISTRO.md), [#27](REGISTRO.md). diff --git a/bitacora/REGISTRO.md b/bitacora/REGISTRO.md index c90db46..eb4c1ab 100644 --- a/bitacora/REGISTRO.md +++ b/bitacora/REGISTRO.md @@ -828,6 +828,24 @@ Se cargan 3.08 h de sesiones con duración comprobable y se reconstruye retrospe --- +## 46 · Jul 16, 2026 · WhatsApp · Erika ↔ Johann · invitación Git, procedimientos y solicitud de avance + +> Evidencia: `../fuentes/2026-07-16 - WhatsApp - Erika invitacion Git procedimientos y avance Etapa1.md`. + +Erika informa que Pedro envió el 15-jul la invitación al repositorio privado y pide validar el acceso. Johann confirma por WhatsApp que ya puede entrar; queda pendiente comprobar que también tenga permisos de escritura. Erika envía por correo los procedimientos de carga de facturas por cliente y Johann confirma recepción. + +Erika solicita un corte del avance de la Etapa 1. Johann se compromete a prepararlo y compartirlo por WhatsApp. + +**Acciones derivadas:** + +- [ ] Johann — validar permisos de escritura en el repositorio de Balam. +- [ ] Johann — revisar el correo y los procedimientos de carga de facturas; documentar reglas o diferencias frente al Discovery. +- [ ] Johann — enviar a Erika el corte de avance de Etapa 1. + +**Estado técnico comprobado al preparar el corte:** repositorio local limpio; solución .NET 10 y cliente BIND compilando; 13/13 pruebas del cliente BIND aprobadas; endpoints base `/health` y `/api/bind/status`. La base de datos, autenticación/roles, sincronización y endpoints de cartera siguen en construcción. + +--- + ## Resumen ejecutivo del hilo (para contexto rápido) ### Datos duros confirmados por Balam diff --git a/fuentes/2026-07-16 - WhatsApp - Erika invitacion Git procedimientos y avance Etapa1.md b/fuentes/2026-07-16 - WhatsApp - Erika invitacion Git procedimientos y avance Etapa1.md new file mode 100644 index 0000000..817f5db --- /dev/null +++ b/fuentes/2026-07-16 - WhatsApp - Erika invitacion Git procedimientos y avance Etapa1.md @@ -0,0 +1,57 @@ +# WhatsApp — Erika Chávez (PM, Balam) ↔ Johann · invitación Git, procedimientos y avance de Etapa 1 + +**Canal:** WhatsApp +**Fecha:** 2026-07-16 +**Evidencia:** captura compartida por Johann el 16-jul-2026. + +## Transcripción + +**Erika · 12:10–12:16 pm** + +- “buenas tardes johan” +- “ayer mi compañero pedro te envio la invitacion para el GIT” +- “puedes revisar si ya puedes acceder” +- “adicional me acaban de liberar los procedimientos de los clientes, ahorita te lo mando por correo” + +**Johann · 12:24 pm** + +- “Buenas tardes” +- “De acuerdo, si ya pude acceder al repositorio, muchas gracias” +- “Enterado” + +**Erika · 12:32 pm** + +- “gracias y quedo a tus ordenes” +- “me pudieras pasar el avance que se tiene de la etapa 1 porfa” + +**Johann · 12:54 pm** + +- “Sí, sin problema, lo preparo y te lo comparto por aquí” + +**Erika · 12:55–1:02 pm** + +- “gracias” +- “ya envie el correo con los procedimientos de cargas de factura de los clientes puedes validarlo si llego el correo por favor” + +**Johann · 1:03 pm** + +- “Recibido, gracias” + +**Erika · 1:28–1:29 pm** + +- “a ti, quedo al pendiente de los avances de la etapa 1” +- “y si necesitas algo mas de nuestro lado me avisas va” + +**Johann · 1:31 pm** + +- “va, en cuanto los tenga te los comparto” + +## Acciones derivadas + +1. Validar que el acceso al repositorio privado permita lectura y escritura. +2. Revisar los procedimientos de carga de facturas enviados por correo y contrastarlos con el flujo levantado en Discovery. +3. Compartir con Erika un corte verificable del avance de la Etapa 1. + +## Observación + +En el chat Johann confirmó que ya podía acceder al repositorio y que recibió el correo. Aún falta conservar el correo o sus adjuntos como evidencia y realizar la revisión de contenido. diff --git a/planeacion/Avance-Etapa1-2026-07-16.md b/planeacion/Avance-Etapa1-2026-07-16.md new file mode 100644 index 0000000..1bb601e --- /dev/null +++ b/planeacion/Avance-Etapa1-2026-07-16.md @@ -0,0 +1,50 @@ +# Corte de avance — Etapa 1 + +**Fecha:** 16-jul-2026 +**Etapa:** Plataforma base y sincronización con BIND +**Rango estimado contractual:** 32–39 h + +## Mensaje breve para Erika + +Hola Erika. Te comparto el Excel actualizado con el avance de la Etapa 1 al día de hoy. + +Como puntos principales, el cliente de consulta de BIND ya quedó terminado e integrado en C#, con las salvaguardas acordadas y 13 pruebas automatizadas aprobadas. La estructura base de la plataforma tiene avance parcial y continúo con el modelo de datos, autenticación/roles, separación por tenant y sincronización de clientes y facturas. + +En el corte tengo 8 h registradas de esta etapa, cuyo rango estimado es de 32–39 h. En el Excel marqué 20% de avance para la Etapa 1: un entregable está terminado y la plataforma base tiene avance parcial. + +Ya recibí los procedimientos que me enviaste. Los voy a contrastar con el flujo levantado en las sesiones para incorporar las reglas por cliente. También validaré que el acceso al repositorio incluya permisos de escritura; si requiero algo adicional de su lado te aviso. + +**Adjunto:** `Plan-actividades-avance-2026-07-16.xlsx` + +## Sustento interno + +### Terminado y comprobado + +- Solución backend .NET 10 y estructura frontend Angular 21. +- Cliente productivo de BIND en C#, solo lectura por defecto. +- Paginación OData, reintentos, control local de cuota y errores conocidos. +- `GET /health`. +- `GET /api/bind/status`. +- **13/13 pruebas automatizadas aprobadas el 16-jul-2026.** + +### En construcción + +- Modelo de datos PostgreSQL y migración inicial. +- Autenticación y matriz de roles. +- Aislamiento multi-tenant y RLS. +- Sincronización incremental de clientes, cotizaciones y facturas. +- Endpoints de clientes, facturas, cotizaciones y cartera. +- Scheduler, bitácora operativa y capa de escritura controlada. + +### Dependencias externas + +- Validar permiso efectivo de escritura en el repositorio privado. +- Revisar los procedimientos por cliente recibidos por correo. +- Cerrar reglas de alta de cliente, fee, envío y Jira→BIND en la sesión del 22-jul. +- Recibir acceso acotado a Azure para despliegue y CI/CD. + +## Criterio de medición + +- **Horas registradas:** 8.00 h de un rango de 32–39 h (**20.5–25% del esfuerzo estimado**). +- **Entregables:** 1 de 8 terminado; plataforma base con avance parcial. +- No se reporta un porcentaje global único porque mezclar horas consumidas con entregables terminados produciría una cifra engañosa. diff --git a/planeacion/Plan-Etapa1.md b/planeacion/Plan-Etapa1.md new file mode 100644 index 0000000..6adc49f --- /dev/null +++ b/planeacion/Plan-Etapa1.md @@ -0,0 +1,128 @@ +# 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](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.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.5–4.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, 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 (4–5 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.5–3.5 h):** la lista `Clients` NO tiene campo de fecha filtrable ⇒ full-scan barato de lista cada ciclo (~1–3 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.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 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 (2–3 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.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í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 (3–4 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 (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 y `timestamptz` son Postgres-specific → verificación manual en runbook. +- **B9 · Datos de prueba + runbook (1–2 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.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) + +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, 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](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). diff --git a/planeacion/Plan-actividades-avance-2026-07-16.bak.xlsx b/planeacion/Plan-actividades-avance-2026-07-16.bak.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7ef858d458b4637b006e75ce823089be6d53ea5d GIT binary patch literal 8306 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�oRPNdqhjoCRir(xWv-GBU$a(ftaj&gC8U`j~Ao{lm! zx8Bod=oXbbGRGWlE-;j3=+ZG(MQyl8kXh9-A^XY1&E7}!Bfs* zCMRl9(ki<6*FBHQrkesWC2~ZA#z0venH9>N@IW~HCWgRHYd&RS3J&r01M`{zOXBgk zl1o75bO{$MF=I~ny!^RNkhu`p|I^mN!oVW{Iunk?t?Q+peCVsSGO(xIWU-na*wn{q z5%4~1Y?48$0H>Q*RD50oJJd~jAs*H8Oo}Q#n@^sXss=_t=DUq_&GoA z3Fe=Ho*L0g()AkPIIk=P%In?G+1$>Bo$aqVdrHX$h7;9)qYQP`Eva}ycF1f-#r5+x zbZLbLMRT&n6B876Gm#G_^~YLD_rN+VLu#q)J^ z^;5Oz6#JGWh_hug-1{GUj~nMkch6{l=ThA~Z6Aa@$raeG1v}8acab0+64Ivjt{pr5 z;i9hr85M)5-fT>EXXD>F3Co)SCon~rML*TXEz0clARQSdf6k)E&%0X5>x`A2VL(9c zOjI+Y^b`s=I)`bc{->gr{#6$aPyj&EtD+eH%4mOei%c9GZ2!t>(dv^n%N&@Gc<)HI zkGF-VBrulLh!#UruC1gDdiC@Mm1rvBubt<-ed0ef5!_FetSsQwaR>dPE-8t*Q^*dC zZJrDo`o>MS1O>Cr8w-5XeUk+(Z{Xs|u@i1YjHVLC??!_k=)NQgOb$*$W( z+2G}JaQt?4$)^)ySnp_G9Pun~)Wa}wzWkslJr6cXwmqf_cxQCe=4At1P67w5Q|-)^ zUn4eNx|!Uo*LcJB_T5W9>YtUTno=G3PwRhStoi{QItQyz6F*aV-C-WH|I;@#WSIr! zuRghb^$*d%eDkk$_}?D-drdaPw<8BhUB6{%rq`>FL*2BF6W6zLuX5ptj--q4B#cIDCk3gB=Tm*YH`wvU1Mr;my%Jw znP%=2mDcLdVf-byE-th7Q98(Nt&K(K87j0XmfBh-r9i7jj*V8?q7Eq~kv5T6U1U*x zKrT~3paS#UF0PTUOssE@exME%seezUeR&%=(gZ^iS}FEV9nIrB5(r=Q8-J~;fAL)U z(hgvVIT&K7>EUSZ^5L&FSdloc+RK4DVEB3Xqm77wqr@Rw7oO`noYZFJWP(6M`((SQ z&5ay%w2jsKP3fak%LgU=(}$?{SDZ9pP5K0KsCic)n(c7fJy4Hn6492)c4z?ngJ{E7 z%nzWXz@fkz6spITQ^UJNF{8wuHm_l$+1*v@DMW)_4oc4oY66MS&ePrmsgonM7y(_r zU8Y<5pdzzjhi0xp4Yo9qrfiaGF>7qk$k^fjhSIr)(tV@!em{P3Rv%*4gq3JD1{b}B z{?=pyVRC^-S18I|;1s+Y%cUV9=lYI;TZie^ZTWjbe#=-07ykJ)bxt_lUglnJw|&@& zqAU4&;;_ZpfCRv@Mqup-vSx~=n(%F z@ISlQe@gjthxTtN{!oECzd{2569k@NF)v-&^6N=*swm0tz`kX}!**X&P5_Se(@1CgZy;Q#;t literal 0 HcmV?d00001 diff --git a/planeacion/Plan-actividades-avance-2026-07-16.xlsx b/planeacion/Plan-actividades-avance-2026-07-16.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..764d86dcad1a9df0e7c72905a81a710467629cbc GIT binary patch literal 11216 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⋙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 jR}TEExF7vr#Q(vFs)}%s)dc{MA+KPF{T5{U Fechas tentativas. Este documento incorpora los ajustes acordados al 15-jul y se mantiene sincronizado con el [Excel oficial consolidado](Plan-actividades-avance-2026-07-10-ajustado.xlsx). Las etapas 2 y 3 se afinan al validar el prototipo y cerrar las definiciones de la Etapa 1. Total estimado: ~6–7 semanas. +> Fechas tentativas. Este documento incorpora los ajustes y el corte de avance al 16-jul y se mantiene sincronizado con el [Excel oficial consolidado](Plan-actividades-avance-2026-07-16.xlsx). Las etapas 2 y 3 se afinan al validar el prototipo y cerrar las definiciones de la Etapa 1. Total estimado: ~6–7 semanas. > > Las horas reales se registran en [`Seguimiento-horas.csv`](Seguimiento-horas.csv); el porcentaje de avance no sustituye ese registro. @@ -14,7 +14,7 @@ | Accesos iniciales: token BIND + manual de marca | 01/07/2026 | 06/07/2026 | Balam / Johann | ✅ Entregados y validados | | Validación técnica de la API de BIND con la cuenta real | 06/07/2026 | 06/07/2026 | Johann | ✅ Realizada: 117 GET, sin escritura | | Prototipo visual navegable | 08/07/2026 | 10/07/2026 | Johann | ✅ Entregado por correo el 10-jul; validación pendiente | -| Repositorio privado GitHub | 13/07/2026 | 16/07/2026 | Balam / Johann | Noé autorizó su creación; usuario `Johann-28`; invitación pendiente | +| Repositorio privado GitHub | 13/07/2026 | 16/07/2026 | Balam / Johann | Invitación recibida y acceso confirmado; falta validar permiso de escritura | | Configuración de infraestructura en Azure (+ staging) | 21/07/2026 | 23/07/2026 | Johann | Apoyo: acceso acotado de Azure (Pedro+Noé); no bloquea trabajo local | | CI/CD + gestión de secretos | 24/07/2026 | 24/07/2026 | Johann | Depende de repo y Azure | | Documento de hallazgos + ADRs + plan refinado | 10/07/2026 | 23/07/2026 | Johann | Borrador creado; cerrar tras las sesiones del 22–23 jul | @@ -24,6 +24,8 @@ ## Etapa 1 +> Plan detallado de ejecución: [Plan-Etapa1.md](Plan-Etapa1.md) + | Actividad | Inicio | Fin | Responsable | Apoyo de Balam | |---|---|---|---|---| | Backend base: autenticación, roles y bitácora de auditoría | 13/07/2026 | 17/07/2026 | Johann | Trabajo local; publicar al recibir el repositorio | diff --git a/planeacion/generar_avance_2026_07_16.py b/planeacion/generar_avance_2026_07_16.py new file mode 100644 index 0000000..e2849cf --- /dev/null +++ b/planeacion/generar_avance_2026_07_16.py @@ -0,0 +1,98 @@ +"""Genera el Excel de avance que se compartirá con Erika el 16-jul-2026.""" + +from copy import copy +from pathlib import Path +from shutil import copy2 + +import openpyxl + + +DIRECTORY = Path(__file__).parent +SOURCE = DIRECTORY / "Plan-actividades-avance-2026-07-10-ajustado.xlsx" +TARGET = DIRECTORY / "Plan-actividades-avance-2026-07-16.xlsx" + + +def find_row(sheet, task): + for row in range(2, sheet.max_row + 1): + if sheet.cell(row, 1).value == task: + return row + raise ValueError(f"No se encontró la actividad: {task}") + + +copy2(SOURCE, TARGET) +workbook = openpyxl.load_workbook(TARGET) +sheet = workbook["Plan"] + +sheet.cell(1, 8, "Horas al 16/07") + +updates = { + "Integracion de plataformas Balam": ( + 0.20, + "Corte de avance al 16-jul; Etapa 0 en validación y Etapa 1 en curso", + ), + "Etapa 0": ( + 0.91, + "22 h registradas; prototipo entregado y validación agendada para el 23-jul", + ), + "Etapa 1": ( + 0.20, + "8 h registradas; 1 de 8 entregables terminado y plataforma base parcial", + ), + "Backend base: autenticación, roles y bitácora de auditoría": ( + 0.30, + "API, health check y DbContext base listos; autenticación, roles y auditoría pendientes", + ), + "Repositorio privado GitHub": ( + 0.90, + "Invitación recibida y acceso confirmado el 16-jul; falta validar permiso de escritura", + ), + "Arquitectura multi-tenant (tenant_id + RLS)": ( + 0.25, + "Diseño técnico inicial; entidades tenant y políticas RLS pendientes", + ), + "Cliente de la API de BIND (reintentos, OData, errores)": ( + 1.00, + "Terminado: cliente C# solo lectura, paginación, reintentos y cuota; 13/13 pruebas aprobadas", + ), + "Modelo de facturas + migraciones + datos de prueba": ( + 0.10, + "Base operativa con SyncCheckpoint y AuditLog; modelo de facturas y migración pendientes", + ), + "Demostración semanal (viernes) + reporte de horas": ( + 0.50, + "Corte del 16-jul preparado; control local mientras se habilita Jira", + ), +} + +for task, (progress, note) in updates.items(): + row = find_row(sheet, task) + sheet.cell(row, 7, progress) + sheet.cell(row, 10, note) + +stage1_row = find_row(sheet, "Etapa 1") +sheet.cell(stage1_row, 8, "=SUM(H12:H24)") + +for row in range(2, sheet.max_row + 1): + sheet.cell(row, 7).number_format = "0%" + +sheet.oddFooter.center.text = ( + "Avance al 16-jul-2026 · Horas reconstruidas pendientes de conciliación con Jira" +) + +# Conserva los estilos y usa el nombre de archivo como título impreso. +sheet.oddHeader.center.text = "Plan de actividades y avance · Proyecto Balam" +for cell in sheet[1]: + cell._style = copy(cell._style) + +temporary = TARGET.with_suffix(".tmp.xlsx") +workbook.save(temporary) + +check = openpyxl.load_workbook(temporary, data_only=False)["Plan"] +assert check.cell(find_row(check, "Etapa 1"), 7).value == 0.20 +assert check.cell( + find_row(check, "Cliente de la API de BIND (reintentos, OData, errores)"), 7 +).value == 1.00 +assert check.cell(find_row(check, "Etapa 1"), 8).value == "=SUM(H12:H24)" + +temporary.replace(TARGET) +print(f"Generado: {TARGET}") diff --git a/planeacion/ordenar_fechas_avance.py b/planeacion/ordenar_fechas_avance.py new file mode 100644 index 0000000..ca3f101 --- /dev/null +++ b/planeacion/ordenar_fechas_avance.py @@ -0,0 +1,113 @@ +"""Ordena las actividades del Excel de avance por fecha de Comienzo, dentro de +cada etapa, conservando estilos, formatos, fórmulas de resumen y encabezados. + +Estructura del plan: + fila 2 = Integración (resumen maestro) -> fija + fila 3 = Etapa 0 (encabezado) -> fija + filas 4-10 = tareas de Etapa 0 -> se ordenan por Comienzo + fila 11 = Etapa 1 (encabezado) -> fija + filas 12-24 = tareas de Etapa 1 -> se ordenan por Comienzo + +Las celdas de fecha son texto ("mié 01/07/26"), así que se parsea el dd/mm/yy +para ordenar cronológicamente (no alfabéticamente, que ordenaría por día de +semana). En empate de Comienzo, se desempata por Fin y luego por el orden +original (estable), para no barajar tareas que arrancan el mismo día. +""" + +import re +from copy import copy +from pathlib import Path + +import openpyxl + +DIRECTORY = Path(__file__).parent +TARGET = DIRECTORY / "Plan-actividades-avance-2026-07-16.xlsx" + +# Grupos de filas de tareas a ordenar (rango inclusivo). Los encabezados +# (2, 3, 11) quedan fuera y no se mueven. +GROUPS = [(4, 10), (12, 24)] + +DATE_RE = re.compile(r"(\d{2})/(\d{2})/(\d{2})") + + +def sort_key_from_cell(value): + """Devuelve (aaaa, mm, dd) desde 'mié 01/07/26'; None si no hay fecha.""" + if value is None: + return (9999, 99, 99) + m = DATE_RE.search(str(value)) + if not m: + return (9999, 99, 99) + dd, mm, yy = (int(g) for g in m.groups()) + return (2000 + yy, mm, dd) + + +def snapshot_row(sheet, row, max_col): + """Captura valor + estilo + formato de cada celda de la fila, y el alto.""" + cells = [] + for col in range(1, max_col + 1): + c = sheet.cell(row, col) + cells.append( + { + "value": c.value, + "style": copy(c._style), + "number_format": c.number_format, + } + ) + height = sheet.row_dimensions[row].height + return {"cells": cells, "height": height} + + +def restore_row(sheet, row, snap, max_col): + for col in range(1, max_col + 1): + c = sheet.cell(row, col) + data = snap["cells"][col - 1] + c.value = data["value"] + c._style = copy(data["style"]) + c.number_format = data["number_format"] + if snap["height"] is not None: + sheet.row_dimensions[row].height = snap["height"] + + +def main(): + wb = openpyxl.load_workbook(TARGET) + sh = wb["Plan"] + max_col = sh.max_column + + for start, end in GROUPS: + snaps = [snapshot_row(sh, r, max_col) for r in range(start, end + 1)] + # Orden estable: por (Comienzo, Fin) usando el índice original como + # tercer criterio implícito de estabilidad de sorted(). + order = sorted( + range(len(snaps)), + key=lambda i: ( + sort_key_from_cell(snaps[i]["cells"][2]["value"]), # col C Comienzo + sort_key_from_cell(snaps[i]["cells"][3]["value"]), # col D Fin + ), + ) + reordered = [snaps[i] for i in order] + for offset, snap in enumerate(reordered): + restore_row(sh, start + offset, snap, max_col) + + tmp = TARGET.with_suffix(".tmp.xlsx") + wb.save(tmp) + + # Verificación: cada grupo queda no-decreciente por Comienzo. + check = openpyxl.load_workbook(tmp)["Plan"] + for start, end in GROUPS: + prev = (0, 0, 0) + for r in range(start, end + 1): + k = sort_key_from_cell(check.cell(r, 3).value) + assert k >= prev, f"Fila {r} fuera de orden: {k} < {prev}" + prev = k + # Los encabezados de etapa siguen en su lugar. + assert check.cell(3, 1).value == "Etapa 0" + assert check.cell(11, 1).value == "Etapa 1" + # Las fórmulas de resumen se conservan. + assert str(check.cell(11, 8).value).startswith("=SUM") + + tmp.replace(TARGET) + print("Ordenado correctamente:", TARGET.name) + + +if __name__ == "__main__": + main() diff --git a/plans/etapa1-plan.md b/plans/etapa1-plan.md new file mode 100644 index 0000000..9b261d1 --- /dev/null +++ b/plans/etapa1-plan.md @@ -0,0 +1,72 @@ +# Plan: Documento "Plan-Etapa1.md" — plan fiel y completo de la Etapa 1 + +## Contexto + +Johann necesita el plan de trabajo de la **Etapa 1 — "Plataforma base y sincronización con BIND"** (32–39 h contractuales, semanas 2–3, ventana 13–24 jul). Hoy 16-jul solo 1 de los 8 entregables contractuales está terminado (el cliente API de BIND, portado a C# con 13 pruebas) y hay 8 h registradas de la etapa; quedan ~24–31 h y la demo semanal del **viernes 18-jul** es el compromiso más cercano. El usuario pidió un plan "lo más fiel posible" a toda la documentación (propuesta comercial v1.2, ADRs de Etapa 0, VALIDACION-API.md, bitácora, seguimiento de horas) — se revisó todo con 2 agentes de exploración y 1 de diseño. + +**Decisión del usuario:** generar SOLO el documento (no arrancar implementación todavía), guardado en el repo privado de gestión `../balam` (nunca en el repo del cliente `balam-plataforma` — regla del CLAUDE.md de ese repo). + +## Qué se hará + +1. **Crear `planeacion/Plan-Etapa1.md`** en `c:\Users\JohannVelazquez\Desktop\Personal\balam` con el contenido detallado abajo. +2. **Agregar 1 línea de referencia** en `planeacion/Plan-actividades.md` (sección Etapa 1) apuntando al nuevo documento: `> Plan detallado de ejecución: [Plan-Etapa1.md](Plan-Etapa1.md)`. +3. Nada más: sin commits al repo del cliente, sin código. (El repo bitácora tiene cambios sin commitear del usuario; NO commitear nada ahí tampoco, solo dejar los archivos.) + +## Contenido del documento Plan-Etapa1.md + +Estructura y contenido esencial (redactado en español MX, estilo de los docs existentes de `planeacion/`): + +### §1 Resumen y estado (al 16-jul) +- Entregable contractual de la etapa (propuesta L142–157): plataforma que ingiere clientes/facturas de BIND, los normaliza, mantiene trazabilidad y habilita capa de escritura controlada. 32–39 h · $19,200–$23,400. +- Horas: 8.00 h registradas (reconstruidas) → quedan ~24–31 h. Tabla de los 8 entregables contractuales con estado real (solo cliente BIND ✅; scaffold/DbContext/entidades semilla ✅ como avance parcial; resto ❌). +- Hechos del repo de código: solución .NET 10 compilando, 13 pruebas verdes, API con /health y /api/bind/status, Angular 21 con tema de marca. + +### §2 Calendario e hitos externos +| Fecha | Hito | +|---|---| +| jue 17-jul | Fin de 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 cliente, fee, envío, alcance Jira→BIND | +| jue 23-jul 7pm | Validación del prototipo Etapa 0 | +| vie 24-jul | CI/CD + secretos · demo/cierre de etapa | + +### §3 Plan de construcción por bloques (B0–B9, fases F1–F6) +Incluir el plan técnico completo producido en el diseño (resumido aquí; el documento lo lleva íntegro): + +- **F1 (16-jul) — B0 Preparación (0.5–1 h) + B1 Modelo de datos (3.5–4.5 h):** docker-compose Postgres 17 local; entidades `Tenant`, `Client`, `Invoice` (modelo central con estados), `Quote`, `QuoteInvoiceLink` (ADR-003), `InvoiceStatusChange` (ADR-004), `WriteOperation`; extender `SyncCheckpoint`/`AuditLogEntry` con TenantId; `BalamDbContext` → `IdentityDbContext`; convenciones de fechas Npgsql (BIND sin zona → `timestamp without time zone`) y decimales; migración `Inicial` + `DbSeeder` idempotente (tenant Balam, 4 roles, 5 usuarios ficticios). +- **F2 (17-jul) — B2 Auth+roles+bitácora (4–5 h) + B3 Sync clientes (2.5–3.5 h):** ASP.NET Core Identity + JWT propio (sin MapIdentityApi, sin refresh tokens en E1); roles `Finanzas/Direccion/Operaciones/Administracion` (sin acentos como identificador); policies nombradas + matriz de acceso por endpoint; `AuditMiddleware` (mutantes + 401/403) + `IAuditLogger` explícito (login, ciclos de sync, escritura) — NO interceptor EF (inundaría con upserts del sync); sync de clientes: full-scan barato de lista (sin campo fecha filtrable) + `SourceHash` + detalle solo nuevos/cambiados + normalización RFC/nombre + dedup por RFC exacto excluyendo genéricos (`DuplicateOfClientId`). +- **F3 (18-jul am) — B4 Sync facturas/cotizaciones + cartera (4.5–5.5 h) → DEMO:** delta `Date ge` desde checkpoint + re-barrido de ventana de facturas abiertas (los pagos caen en facturas viejas) + detalle solo nuevas/cambiadas con tope por ciclo; mapeo de estados plataforma (Emitida=prefactura, Vigente, Vencida=derivada con fecha MX, Pagada, Cancelada) como **función pura con pruebas** (frontera Emitida/Vigente a confirmar el 22-jul); detección de pagos por transición → `InvoiceStatusChange` con fecha de DETECCIÓN (ADR-004); endpoints `GET /api/cartera` (agregados POR MONEDA, nunca mezclarlas), `/api/invoices`, `/api/clients`, `/api/quotes`, `POST /api/sync/run`; **sync inicial del histórico (>1,000 facturas) se corre el jueves en la noche, nunca en vivo**. +- **F4 (21-jul) — B5 Scheduler (2–3 h) + B6 RLS (1.5–2.5 h):** `BackgroundService` + `PeriodicTimer` 15 min (NO Quartz/Hangfire — un solo job, estado en checkpoints), `SemaphoreSlim` anti-solape, guarda de cuota vía `BindQuotaTracker` (reserva configurable), backfill gradual de detalle histórico; RLS REAL de Postgres (contractual): políticas `tenant_isolation` por tabla con `current_setting('app.tenant_id')` + `FORCE` + `WITH CHECK`, `TenantConnectionInterceptor` con `set_config`, más filtro global EF (`ITenantOwned`) como segunda capa; fuera: CRUD tenants, resolución por request. +- **F5 (22-jul, post-sesión) — B7 Capa de escritura controlada (3–4 h):** pipeline `Draft → DryRun → Confirm` persistido en `WriteOperations`, todo auditado; `BindWriteClient : BindClient` (reusa `RequestAsync` protected y sus salvaguardas); feature flags `BindWrite:Enabled=false` default; PPD default, PUE solo con confirmación explícita (salvaguarda 4); CERO llamadas reales a BIND — shapes provisionales marcados TODO hasta doc del portal con Pedro; conversión mock registra `QuoteInvoiceLink`. +- **F6 (23–24 jul) — B8 Suite de pruebas (2.5–3.5 h) + B9 datos de prueba + runbook (1–2 h):** nuevo proyecto `tests/Balam.Api.Tests` con `WebApplicationFactory` + **SQLite in-memory** (no EF InMemory — no valida FKs/únicos; no Testcontainers — exige red/Docker en tests); cobertura contractual: matriz de roles, sync (delta/checkpoint/detección de pago), multimoneda, salvaguardas de escritura; `DevDataSeeder` ficticio multimoneda/multi-estado (respaldo de demo); runbook de demo. + +### §4 Presupuesto de horas +Tabla B0–B9 (nominal ~27.5 h, rango 25–31) + recortes identificados (≈ −5 h): audit endpoint y quotes-sync pueden moverse post-demo; dedup solo RFC; backfill mínimo. B6 (RLS) NO recortable a cero por ser contractual. + +### §5 Decisiones de arquitectura (con porqués, citables) +Scheduler BackgroundService vs Quartz; RLS real en dos capas; Identity+JWT sin refresh tokens (diferidos a Etapa 2 con la UI); bitácora middleware+servicio vs interceptor EF; SQLite en pruebas con costo aceptado (RLS/timestamptz se verifican manual en runbook); trampas Npgsql (fechas), "hoy" con zona México, `partial class Program`. + +### §6 Bloqueado hasta el 22-jul y fuera de alcance +- Bloqueado: escritura real a BIND (ADR-001), reglas de alta de cliente/fee/envío, alcance Jira→BIND, semántica exacta Emitida/Vigente, mapeo `CFDIUse`→SAT. +- Fuera de Etapa 1: UI (E2), refresh tokens (E2), recordatorios a clientes (Anexo B), multiempresa (ADR-005), fecha valor de pagos/REP (ADR-004), aging/tablero (E3), conciliación, CI/CD+Azure (actividades propias 21–24 jul), envío de PDF. + +### §7 Riesgos y gestión (no técnico, en el mismo doc) +- Riesgos: primera corrida de sync en vivo (mitigado: jueves noche); filtro `CreationDate` de Quotes no validado (fallback full-scan); repo visible al cliente (solo datos ficticios, sin co-autoría en commits). +- Pendientes de gestión que tocan la etapa: correo formal a Noé (salvaguardas — borrador listo), 5 preguntas técnicas a Pedro (REP/pagos, `CFDIUse`, `/xml`, webhooks, token Power BI), borrar `bind_token_api.txt` del disco, registrar horas diario en `Seguimiento-horas.csv`, preparar sesión 22-jul (ya existe `Sesion-Etapa1-2026-07-22.md` — referenciarla). + +## Archivos a crear/modificar + +| Archivo | Acción | +|---|---| +| `c:\Users\JohannVelazquez\Desktop\Personal\balam\planeacion\Plan-Etapa1.md` | Crear (documento completo) | +| `c:\Users\JohannVelazquez\Desktop\Personal\balam\planeacion\Plan-actividades.md` | Agregar 1 línea de referencia en la sección Etapa 1 | + +## Verificación + +1. El documento existe, abre bien en Markdown y sus tablas renderizan (revisión visual). +2. Cifras contractuales cuadran con la propuesta: 32–39 h, entregables L148–155, roles y stack (verificar contra `propuesta/00 - PROPUESTA-COMERCIAL.md`). +3. Cifras de horas cuadran con `Seguimiento-horas.md` (8.00 h E1, 30 h totales). +4. Fechas cuadran con `bitacora/REGISTRO.md` #42–#43 (22-jul, 23-jul, Azure sem 21, CI/CD 24). +5. El enlace desde `Plan-actividades.md` navega al nuevo documento. +6. Confirmar que NADA se escribió en el repo del cliente (`balam-plataforma` limpio: `git -C ...balam-plataforma status`).