Commit Graph
16 Commits
Author SHA1 Message Date
urieljarethandClaude Opus 5 edb500f517 Fix: fijar mcp a la serie 1.x y evitar que su fallo tumbe el REST
El primer rebuild del api en dos semanas trajo mcp 2.0.0, que elimino
Server.list_tools(). app/mcp/server.py lo usa como decorador en su linea 27,
asi que el import lanzaba AttributeError al arrancar.

El agravante: main.py envolvia el montaje del MCP en `except ImportError`.
Un AttributeError no es ImportError, asi que se escapaba y tumbaba toda la
aplicacion. En produccion el contenedor quedo en crash-loop y el REST dejo de
responder por completo, no solo el MCP.

Dos arreglos:
- requirements.txt fija `mcp>=1.28.1,<2.0.0`. La imagen anterior que llevaba dos
  semanas sana tenia 1.28.1; el rango abierto `>=1.0.0` permitio el salto mayor.
- main.py captura cualquier excepcion al montar el MCP y la registra. El servidor
  MCP es opcional; el REST no. Si el MCP no monta, la API sigue de pie.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 15:45:53 -06:00
urieljarethandClaude Opus 5 9023384de9 Fase 0: cerrar el MCP, unificar totales y separar el contexto interno del cliente
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]>
2026-07-28 14:50:03 -06:00
urieljarethandClaude Opus 5 0ff783fd65 Spec: diseño de Fase 0 y Fase 1 del plugin de propuesta consultiva con IA
Convierte el análisis de negocio de docs/BI-propuesta-consultiva-IA.md en una
especificación cerrada para las dos primeras fases del roadmap.

El análisis previo destapó cuatro problemas que el documento de negocio no
anticipaba, los cuatro verificados en disco:

- POST /mcp no tiene autenticación (api/main.py:107) y _obtener_cotizacion hace
  SELECT c.* sin lista blanca, con dominio público en producción. Publicaría la
  columna de notas internas sin tocar una línea de código.
- No existe una fuente de verdad para los totales: el cálculo está duplicado en
  siete consumidores y el IVA está hardcodeado como * 1.16 en PDF y Excel.
- El Excel es un documento del cliente, no una herramienta interna. El diseño
  inicial proponía poner ahí las notas internas; se corrigió.
- Las transcripciones se sincronizarían a la nube de MEGA: .megaignore solo
  excluye .next, node_modules y .turbo.

También reconcilia nueve contradicciones de contrato entre los diseños
explorados (clave de partida, forma de la evidencia, nombres del valor anual)
que habrían hecho que la capa de validación validara el vacío.

Actualiza AGENTS.md, que había quedado desfasado: 14 modelos y 10 migraciones
(decía 13 y 3), el registro de horas, la doble propuesta, los modelos de cobro
y la convención de no usar diálogos nativos del navegador.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 14:20:03 -06:00
urieljarethandClaude Opus 4.8 26e6d3a9bf UI: reemplazar diálogos nativos del navegador por modales/avisos propios de la plataforma
Los confirm()/alert()/prompt() nativos se veían fuera de la marca ("<dominio> dice…").
Se agrega un DialogProvider (montado en el layout autenticado) con su estilo visual y se
consume via hooks useConfirm / usePrompt / useToast:
- confirmaciones -> modal propio (con variante "peligro" para eliminaciones)
- prompts (crear paquete / fase) -> modal con input
- alerts de error/éxito -> toasts

Reemplazados todos los diálogos nativos: RegistroHorasPanel, CatalogoClient,
ConfiguracionClient, CotizacionForm, ExportButtons, ListDeleteButton, DeleteButton,
CambiarEstadoButtons. Validado E2E (Playwright): al revertir una partida pagada aparece
el modal propio y NO se dispara el confirm nativo.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-11 14:51:22 -06:00
urieljarethandClaude Opus 4.8 1aab45f29c Registro de horas: hacer descubrible como regresar una partida pagada a "Por pagar"
El boton de revertir solo aparecia en la vista "Pagadas", pero el filtro por defecto
es "Por pagar", que oculta las pagadas -> el usuario no encontraba como corregirlas.
Ahora:
- Las tarjetas de resumen "Por pagar" / "Pagado" son clicables y filtran la tabla; la
  de "Pagado" muestra el hint "Ver / editar" y lleva a las partidas pagadas.
- Cuando no hay pendientes pero si pagadas, el estado vacio ofrece un boton
  "Ver N pagada(s) para editar o regresar a Por pagar".
- Los botones de accion por fila ahora llevan texto ("Pagada" / "Por pagar"), no solo
  icono, para que revertir sea evidente.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-11 14:27:25 -06:00
urieljarethandClaude Opus 4.8 63b14fc8b8 Registro de horas: accion explicita para regresar una partida pagada a "Por pagar"
El badge de estado deja de ser un toggle (facil de disparar por error) y pasa a ser
un indicador. En su lugar, la columna de acciones muestra un boton claro segun el estado:
"Marcar como pagada" (pendientes) o "Regresar a Por pagar" (pagadas, con confirmacion).
Asi se corrigen los casos donde una partida se marca como pagada por error. El backend
ya limpiaba la fechaPago al regresar a por_pagar; solo cambia la UX del panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-11 11:31:18 -06:00
urieljarethandClaude Opus 4.8 3824dd00be Nota de horas: datos de pago (bancarios) + periodo en el nombre del archivo
- Las notas "por pagar" ahora incluyen los datos bancarios de cobro configurados
  (Transferencia Nacional/Internacional: cuenta, CLABE, beneficiario, RFC, banco, SWIFT),
  igual que el PDF de cotizacion. Solo se muestran en las notas por pagar.
- El nombre del PDF incluye el periodo cobrado (fecha minima a maxima de los registros),
  formato DD-MM-YYYY_DD-MM-YYYY. Ej: "Nota de horas por pagar UJ2606AG777 - 24-06-2026_24-06-2026".

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-08 12:46:35 -06:00
urieljarethandClaude Opus 4.8 feb88de203 Nota de horas: generar PDF en el servidor (sin URL del navegador)
Imprimir el HTML de la nota desde el navegador siempre inserta encabezado/pie con la
URL de la cotizacion, fecha y no. de pagina (el @page{margin:0} no lo evita al imprimir
un iframe). Se reemplaza por un PDF generado en el servidor con pdfkit, que no lleva
ningun encabezado del navegador.

- src/lib/nota-horas-pdf.ts: generateNotaHorasPDF (logo, titulo segun estado, tabla
  detalle/agrupada, columna Estado en "todas", totales con IVA opcional).
- API POST /api/cotizaciones/[id]/nota-horas: filtra por estado/periodo, agrupa y
  calcula totales del lado servidor y devuelve el PDF.
- Panel: el boton del modal pasa de "Imprimir" a "Descargar PDF" (baja el PDF del
  servidor); se conserva la vista previa HTML en pantalla.
- pdf-generator: toPngBuffer ahora convierte webp/otros a PNG con sharp, así el logo
  (webp) tambien aparece en el PDF de la cotizacion.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-08 10:59:04 -06:00
urieljarethandClaude Opus 4.8 89d7028044 Nota de horas: al imprimir, omitir el encabezado con la URL de la cotizacion
Se agrega @page{margin:0} al HTML de la nota para que el navegador no inserte sus
encabezados/pies automaticos (URL de la cotizacion, fecha, no. de pagina) al imprimir
o guardar como PDF. El margen visual del contenido se pasa a padding del body.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-08 10:22:58 -06:00
urieljarethandClaude Opus 4.8 34419d1752 Registro de horas: la nota por defecto excluye lo ya pagado
El filtro de estado por defecto pasa de "Todas" a "Por pagar", de modo que el
documento que se emite/manda al cliente ("Nota de horas por pagar") solo lista las
horas pendientes y nunca las ya pagadas. "Pagadas" (recibo) y "Todas" (estado de
cuenta) quedan como vistas internas seleccionables.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-08 09:15:34 -06:00
urieljarethandClaude Opus 4.8 d83a80c85a Registro de horas: estado de pago por partida (por pagar / pagada)
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]>
2026-07-08 08:33:14 -06:00
urieljarethandClaude Opus 4.8 8b1e420d4a Recuperar feature de registro de horas / cobro por tiempo (RegistroHoras)
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]>
2026-07-08 02:11:57 -06:00
urieljarethandClaude Opus 4.8 4d550c5963 Fix docker-compose.yaml: quitar BOM UTF-8 y mojibake que rompían Coolify
El archivo raíz docker-compose.yaml (usado por Coolify como "Docker Compose
Location") tenía un BOM UTF-8 al inicio y comentarios con doble codificación.
El parser YAML de Symfony en Coolify fallaba al parsearlo
(Yaml::parse(): argument must be string, null given) dejando docker_compose
en null y abortando el despliegue. Ahora es una copia limpia (UTF-8 sin BOM)
de docker-compose.coolify.yml.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-08 00:00:24 -06:00
urieljareth e1f3263c9f Accept DB_DATABASE (Coolify convention) in addition to DB_NAME
Coolify auto-injected env vars from an attached Postgres use DB_DATABASE,
but the cotizador code read DB_NAME. Now both are accepted (DB_DATABASE
takes priority) across db.ts, seed.ts, and prisma.config.ts so attaching a
managed Postgres in Coolify Just Works.
2026-07-07 23:17:15 -06:00
urieljareth 2157de6b89 Add docker-compose.yaml for Coolify default detection
Coolify's dockercompose build pack looks for /docker-compose.yaml by default.
The repo had /docker-compose.coolify.yml, causing deploy to fail with
'Docker Compose file not found at: /docker-compose.yaml'. Adding the expected
filename as a copy of docker-compose.coolify.yml so default detection works.
2026-07-07 22:53:14 -06:00
urieljarethandClaude Fable 5 ab190560d2 Derivar DATABASE_URL desde DB_* en prisma.config.ts
En despliegues Dockerfile (Coolify) no existe DATABASE_URL y
prisma migrate deploy abortaba al arrancar. Ahora la URL se arma
desde las mismas DB_* que usan la app y el seed.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 08:47:45 -06:00