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]>
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]>
- 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]>
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]>
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]>
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]>
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]>
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]>
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.
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.
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]>
- 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]>