La capa de economia existe para que el dinero viva en un solo sitio y nunca
cruce hacia el prompt. El pipeline recibe de ahi solo refPartida y nombre.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Primera fase del plugin de propuesta consultiva (docs/superpowers/specs/
2026-07-28-propuesta-consultiva-ia-fase0-fase1-design.md). Recupera el contexto
humano que hoy se captura y se descarta, y cierra los bloqueadores que el
analisis previo destapo.
Seguridad (lo mas urgente):
- POST /mcp no tenia NINGUNA autenticacion: cero Depends() en main.py, mientras
el servicio recibe dominio publico en produccion (SERVICE_FQDN_API_8000).
Cualquiera en internet podia leer y escribir cotizaciones. Ahora exige
require_auth, que ya existia en app/auth.py y no se estaba usando ahi.
- _obtener_cotizacion hacia SELECT c.* y devolvia dict(cot) al agente, asi que
cualquier columna nueva se publicaba sola. Ahora usa lista blanca espejo de
CotizacionResponse, excluyendo observacionesInternas.
Totales:
- Nueva calcularTotalesCotizacion() en calculators.ts como fuente de verdad
unica. El calculo estaba duplicado a mano en siete consumidores.
- conIva() sustituye a los `* 1.16` hardcodeados de pdf-generator y
excel-builder, que ignoraban IVA_RATE y el flag incluirIva.
Contexto humano:
- Campo nuevo Cotizacion.observacionesInternas. La migracion es PURAMENTE
ADITIVA: no mueve ni una fila. El movimiento de datos no hace falta porque la
unica fila de produccion con observaciones ya contiene texto dirigido al
cliente, y separarlo en dos despliegues mantiene el rollback limpio.
- Dos textareas visualmente inconfundibles en el formulario.
- observaciones se imprime por primera vez en el PDF y el Excel; el dato ya
viajaba hasta las rutas de borrador y se tiraba.
- observacionesInternas solo se ve en la app. La garantia es estructural: el
campo no existe en CotizacionPDFData ni en ExcelData, asi que el generador no
puede filtrarlo aunque alguien lo intente.
Bugs vecinos:
- orderBy explicito en las cuatro rutas de export: el PDF asume las partidas
agrupadas por fase y sin orderBy podia diferir del Excel del mismo envio.
- El PDF de borrador leia solo el branding, no los datos bancarios, e imprimia
los hardcodeados del generador.
- detalleModelo local en PDF y Excel omitia la rama "demanda": esa partida
salia en $0 y sin explicacion. Ahora delegan en la version canonica.
- Los bonos salen de la tabla Bono; la lista hardcodeada queda de respaldo y su
texto ya no coincidia con el seed.
- La palomita de los bonos mide 0pt en las fuentes base de PDFKit (verificado),
o sea que salia como dos espacios. Sustituida por una vineta.
Ademas: zod pasa a ser dependencia declarada. Se importaba en schemas.ts y
resolvia transitivamente, asi que un npm ci --omit=dev reventaba.
Verificado con build, lint y 22 comprobaciones funcionales sobre PDF y Excel
reales (texto del cliente presente, texto interno ausente incluso inyectandolo
a la fuerza, texto largo multipagina, y totales con y sin IVA).
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Permite separar las horas ya cobradas de las pendientes para que los totales no se
acumulen y poder emitir la nota por pagar al cliente y conservar el recibo de lo pagado.
- Modelo: RegistroHoras.estadoPago ("por_pagar" default | "pagada") + fechaPago
(se sella al marcar pagada, se limpia al revertir). Migración idempotente.
- API: PATCH /api/cotizaciones/[id]/horas/[registroId] para marcar por pagar/pagada.
- calculators: ESTADOS_PAGO_HORAS + resumenPagoHoras() que suma por separado
pendiente vs pagado.
- Panel: resumen "Por pagar" vs "Pagado" siempre visible (con IVA si aplica),
badge/toggle de estado por fila, filtro de estado (Por pagar/Pagadas/Todas) que
además define el documento: "Nota de horas por pagar", "Recibo de horas pagadas"
o "Estado de cuenta de horas".
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
La cotización UJ2606AG777 (aprobada, "Agentes IA") se cobra por hora bajo
demanda, pero la rama desplegada (main) no incluía la feature de registro de
horas que sí existía en desarrollo (rama master): el cargo de horas de junio no
se veía en la web y los proyectos por tiempo no mostraban sus cobros en el panel
individual.
Se porta la feature de forma quirúrgica (sin arrastrar cambios no relacionados):
- Modelo RegistroHoras + columnas Cotizacion.incluirIva, Cliente.rfc,
ServicioCotizado.beneficios (migración idempotente para la web).
- calculators.ts: modelo de cobro "demanda", helpers de horas
(calcularHorasRango, agrupación por día/semana/mes, notas de pago).
- RegistroHorasPanel: panel para registrar/editar/borrar horas y previsualizar
la nota de pago con branding. Solo aparece en cotizaciones aprobadas con cobro
por tiempo (horas/retainer/demanda).
- PreciosEditables: los servicios "demanda" muestran la tarifa/hr en vez de $0.
- API /api/cotizaciones/[id]/horas (+ /[registroId]) para el CRUD de registros.
- Handlers de cotizaciones: persisten incluirIva/rfc/beneficios, "demanda" nunca
suma al total, y fast-path para el cambio de estado (arregla CambiarEstado que
fallaba la validación al enviar solo { estado }).
- .megaignore para evitar que MegaSync corrompa node_modules/.next.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Recupera el trabajo revertido (doble propuesta, retainer, niveles, API Python) desde el stash de GitHub Desktop
- Dockerfile multi-stage para Next.js (migraciones automáticas al arrancar, seed opcional via RUN_SEED)
- docker-compose.coolify.yml: postgres + web + api con healthchecks y SERVICE_FQDN_*
- Endpoint público /api/health (verifica BD) para healthchecks
- seed.ts parametrizado: conexión DB_* y credenciales SEED_* por entorno (sin passwords hardcodeadas)
- .env.example, .dockerignore, uvicorn con --proxy-headers
- Limpieza: .xlsx y __pycache__ fuera del repo
Co-Authored-By: Claude Fable 5 <[email protected]>