docs(crm): abre el changelog del repo y versiona el plan de T2026-08-046

El CRM no tenia CHANGELOG.md. Se crea con el formato del de EFC para que un ticket
que cruza los dos productos se lea igual de los dos lados, y se abre con la entrada
de T2026-08-046 (fases 5-7).

Va en un PR aparte del codigo a proposito: el changelog es el unico archivo
garantizado en colision entre tickets, y metido en cada PR de codigo convierte cada
merge en una resolucion de conflictos en vez de una revision.

La entrada dice tambien lo que NO esta: el lado de EFC del carril todavia no aterriza,
las variables de entorno de produccion quedaron sin tocar y el contrato entre repos
esta afirmado solo desde el CRM. Un changelog que afirma un entregable completo cuando
esta a medias es peor que ninguno.

Se versiona ademas el documento Prompt del ticket en docs/planes/, siguiendo la
convencion de EFC, para que las decisiones del codigo tengan su porque a la vista sin
depender de una carpeta de corrida. Un dato se sustituyo al versionar: un caso de
prueba traia un token con forma de llave de pedimento real y aqui va como dummy; el
caso prueba que esa FORMA se rechace, asi que el numero concreto no aportaba nada.

Refs: T2026-08-046
This commit is contained in:
2026-08-10 07:50:41 -06:00
parent c6f18013b3
commit 59fb371f5d
2 changed files with 1560 additions and 0 deletions

90
CHANGELOG.md Normal file
View File

@@ -0,0 +1,90 @@
# Changelog — CRM Agentes de Carga
Historial de cambios por ticket (más reciente arriba). Cada entrada: fecha, ticket, tipo, repos
afectados, qué se hizo y por qué. El formato es el mismo que el de `EFC/backend/CHANGELOG.md`, para
que un ticket que cruza los dos productos se lea igual de los dos lados.
Este archivo lo abre la corrida SUNRISE del 2026-08-07: el repo no tenía changelog. Las entradas
llegan en un PR de documentación aparte, nunca dentro del PR de código — el `CHANGELOG.md` es el
único archivo garantizado en colisión entre tickets, y metido en cada PR convierte cada merge en una
resolución de conflictos.
---
## T2026-08-046 (feature, fases 5-7) — Expediente electrónico del CRM y entrega de documentos a EFC
- **Fecha:** 2026-08-07
- **En corto:** Cada solicitud de servicio nace ya con su expediente y su folio propio, que el usuario
ve en cuanto guarda. Los documentos de un embarque se adjuntan en un solo paso y viajan solos al
expediente electrónico; si ese sistema no responde, el documento **no se pierde**: se queda guardado
aquí, la pantalla lo muestra como pendiente de enviar, y se entrega solo cuando el otro lado vuelve.
Quien lo necesite puede abrirlo desde el CRM aunque el archivo ya viva del otro lado.
- **Tipo:** feature
- **Repos:** CRM (backend + frontend). **Las fases 1-4, del lado de EFC, son de otra corrida y todavía
no están** — ver «Lo que falta» al final de la entrada.
- **Branch:** `feature/T2026-08-046` · **PR:** (pendiente de abrir a mano)
- **Inicio:** 2026-08-07T17:40:46 · **Fin:** 2026-08-10T08:00 (la corrida estuvo tres días en pausa
entre medias; el detalle está en `SUNRISE/2026-08-07/Diario-2026-08-07.md`)
- **Con dos migraciones, generadas y SIN aplicar:** `e6f7a8b9c0d1_crm_expedientes.py` y
`f7a8b9c0d1e2` (el outbox del carril). `alembic heads` devuelve **una sola hoja** después de las
dos; el `down_revision` se verificó con `alembic heads`, no se supuso.
- **Contexto:** el CRM guardaba los documentos de sus embarques en su propio almacén y ahí se
quedaban. El expediente electrónico —donde de verdad se consultan— vive en EFC, así que había que
volver a subir cada documento a mano del otro lado, o no subirlo. Y el enlace entre un expediente
del CRM y un pedimento de EFC no podía colgarse de una columna nueva en la tabla de pedimentos: el
expediente del CRM nace **antes** de que exista pedimento alguno.
- **Qué se hizo:**
- **El expediente y su folio.** Una solicitud de servicio crea su expediente en la misma
transacción del alta: si algo falla, no queda ni la solicitud ni un folio quemado. El consecutivo
lo lleva un contador con bloqueo por `(cliente, empresa, mes)` que **reinicia cada mes**, en una
sola sentencia atómica —sin leer-modificar-escribir— para que dos altas simultáneas no puedan
sacar el mismo número. La restricción de unicidad del folio lo garantiza aunque el contador
fallara.
- **El carril de entrega hacia EFC.** Clon del carril que ya usa Anexo22, con sus tres capas de
reintento: el cliente HTTP reintenta lo transitorio y **corta en seco** ante un error del
llamador, la fila pendiente se reintenta con límite de intentos, y un barrido periódico recoge lo
que se quedó atrás. Cuatro capas de idempotencia impiden que un reintento duplique un documento
del otro lado. **La llamada a EFC nunca ocurre dentro de la petición del usuario**: se encola y se
despacha.
- **La subida de un paso y el proxy de descarga.** Adjuntar un documento guarda, registra y encola
en una sola operación, y responde **aunque EFC esté apagado**. Para abrirlo, el CRM hace de proxy
con streaming: el otro sistema nunca entrega una dirección de descarga reutilizable, y reescribir
una dirección ya firmada la invalida.
- **La pantalla.** La ficha del embarque muestra cada documento con su estado —«Pendiente de
enviar», «En expediente», «No se pudo enviar»— y un botón para reintentar el envío cuando algo
falló, sin que nadie tenga que entrar a la base.
- **Tres defectos preexistentes corregidos de paso**, todos en la subida de archivos y todos
autorizados por el ticket: un archivo se leía **entero en memoria** antes de comprobar si excedía el
tamaño máximo (un archivo de 2 GB se bufferizaba solo para rechazarlo); no había lista de
extensiones permitidas, a diferencia del avatar y del centro de ayuda, que sí la tienen; y firmar
una dirección de descarga solo comprobaba el prefijo de la empresa, lo que permitía alcanzar
**cualquier** objeto de esa empresa —facturas, certificados, importaciones— con el permiso más
básico del CRM.
- **Un defecto propio, encontrado y corregido antes de mergear:** el proxy de descarga comprobaba la
respuesta de EFC **demasiado tarde**, ya dentro del envío del archivo. Para entonces la respuesta
del CRM ya había salido como «correcta», así que un documento que EFC no encontraba llegaba al
usuario como un archivo vacío en vez de como un error. Ahora la comprobación ocurre antes de
empezar a responder. Lo destapó una prueba que el ticket pedía y que la fase original no dejó
escrita.
- **Verificación:** `pytest tests/ -q`**215 pasan, 1 se salta**, contra las 70 del punto de
partida; ningún rojo nuevo. En el frontend, la revisión de tipos y las pruebas unitarias quedan
**exactamente** en los mismos números rojos que ya tenían antes de este trabajo (38 y 2, ambos
heredados y ajenos al ticket). Cada verde se vio fallar primero: seis roturas deliberadas,
comprobando que la prueba se pusiera roja nombrando lo que faltaba, y restauradas.
- **Lo que falta, y hay que saberlo antes de mergear:**
- **El lado de EFC (fases 1-4) no está.** Sin él, el carril entrega a un endpoint que todavía no
existe: los documentos se guardan en el CRM y quedan «pendientes de enviar» hasta que aterrice.
Eso es degradar limpio, no romper — pero nadie debería mergear esto creyendo que el circuito está
cerrado de los dos lados.
- **Las variables de entorno de producción no se tocaron.** El worker y el proceso periódico de
producción no ven la dirección de EFC, así que allá el carril queda **apagado** hasta que alguien
las agregue a mano. Está en la lista de pasos manuales del reporte de la corrida.
- **El contrato entre los dos repos está afirmado solo de este lado.** El archivo que lo describe
vive en el CRM y la suite del CRM lo comprueba; EFC debe afirmar su mitad contra una copia
idéntica cuando lleguen sus fases.

File diff suppressed because it is too large Load Diff