Commit Graph
27 Commits
Author SHA1 Message Date
urieljarethandClaude Opus 5 d20007d252 Propuesta IA: el documento lista TODAS las partidas de la cotizacion
Encontrado con datos reales de produccion. UJ2606UR001 tiene 58 partidas y el
schema acota alcance a 40, asi que 18 servicios cotizados desaparecian del
documento del cliente en silencio: aparecian en el PDF economico pero no en el
consultivo, para el mismo envio.

El arreglo no es subir el tope. El generador ahora recorre las partidas de la
COTIZACION, no las que la IA alcanzo a describir, y usa la prosa de la IA cuando
existe. Si falta, imprime el detalle del catalogo (entregables y tiempo de
entrega) como respaldo. La completitud la manda la base de datos; la IA solo
aporta la redaccion. Es el mismo principio que ya rige para el dinero.

Verificado contra la propuesta real que genero produccion: 58 de 58 presentes,
cero faltantes.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 20:40:43 -06:00
urieljarethandClaude Opus 5 7682fcb86b Coolify: pasar las variables MINIMAX_* al contenedor web
El compose filtra que variables llegan al contenedor. Sin declararlas aqui,
ponerlas en el panel de Coolify no habria servido de nada: el boton de generar
propuesta habria fallado con 'Falta MINIMAX_API_KEY' aun estando configurada.

Editados los dos archivos, como exige AGENTS.md: docker-compose.yaml es una copia
identica de coolify.yml que existe para la deteccion por defecto de Coolify, y
editar solo uno despliega el viejo.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 19:03:35 -06:00
urieljarethandClaude Opus 5 74c0374a2a Propuesta IA: correcciones encontradas probando contra la API real de MiniMax
Tres defectos que solo se veian llamando al modelo de verdad.

1. min(2) en los factores obligaba a inventar relleno. Con una dimension sin
   cifras, MiniMax produjo "herramienta_de_seguimiento_actual=0 x canal=1" solo
   para satisfacer la restriccion. Ahora los dos factores se exigen unicamente
   cuando hay montoAnualMXN.

2. El modelo OMITE montoAnualMXN en vez de mandar null explicito, que es lo
   natural para un LLM. Exigirlo presente quemaba los tres intentos del paso.
   Ahora es .default(null) y la omision se tolera.

3. Sin confianza por factor, el modelo la metia dentro del nombre
   ("tasa_conversion (por_validar)=0.1"). Ahora es un campo.

Y el hallazgo que mas importa, porque es comercial y no de formato: el modelo
puede OMITIR un factor y aun asi cuadrar la aritmetica. En una corrida calculo
40 mensajes x 0.5 sin contestar x 52 semanas x 3,000 de utilidad POR CLIENTE =
3,120,000, asumiendo que cada mensaje sin responder es un cliente perdido. R5 lo
acepto porque los factores si multiplican al monto: R5 no puede ver lo que falta.

El ratio habria dicho "subcotizado" con el denominador inflado diez veces. Como
no se puede impedir que el modelo produzca estimaciones plausibles y erroneas, lo
que se hace es que el asesor las cache de un vistazo:
- R5b avisa cuando una cifra no tiene ni un factor confirmado.
- El anexo interno desglosa cada factor con su confianza, y alerta en rojo si el
  valor descansa entero en estimaciones.

Ademas, el presupuesto de reintentos sube (5 en extraccion, 4 en los otros dos):
MiniMax se equivoca de array de vez en cuando con schemas anidados, y la
extraccion es el paso fundacional. Una corrida real necesito los 5.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 18:59:42 -06:00
urieljarethandClaude Opus 5 ce3a38f89d Propuesta consultiva con IA: rutas, panel de edicion y tercer documento
Cierra la funcionalidad: el Cotizador ahora ofrece tres descargas por cotizacion
—Excel, PDF economico y PDF consultivo con marca— mas un anexo interno aparte.

Rutas: POST genera, GET lee la ultima, PATCH guarda la edicion del asesor
(revalidando, porque una edicion manual puede introducir una fuga o un USD), y
GET /pdf descarga, con ?anexo=1 para el interno.

Panel: transcripcion opcional, avisos de validacion, campos editables (titulo,
subtitulo, hallazgos, alcance, exclusiones y beneficios) y las descargas.
Los avisos van deliberadamente ARRIBA de los botones de descarga: el objetivo es
que revisar sea mas facil que aprobar.

El anexo interno lleva la ponderacion: ratio precio/valor sobre el desembolso
del primer ano con IVA, su lectura (subcotizado / en rango / alto / objecion
probable), conteo de hallazgos y evidencia, red flags y notas para el asesor.
Va en archivo separado con banda roja "NO ENVIAR", nunca como seccion oculta.

Verificado con 6 suites (114 comprobaciones), typecheck, lint y build. Las mas
importantes son las estructurales: ningun schema declara un campo de dinero de
E3, ningun prompt filtra precios ni cuids, y el documento del cliente no
contiene notas internas ni red flags aunque se inyecten a la fuerza.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 18:25:39 -06:00
urieljarethandClaude Opus 5 91db8cc55c Propuesta IA: pipeline de tres pasos y generadores PDF
El documento del cliente sigue la arquitectura argumental de la plantilla de
referencia pero en tema claro con PDFKit: la plantilla original es oscura, y en
impresion eso depende de una casilla del navegador desactivada por defecto.

El anexo interno va en archivo separado, no como seccion oculta.

44 comprobaciones sobre PDFs reales: contenido presente, notas internas y red
flags ausentes del documento del cliente, precios inyectados por el codigo,
branding invalido tolerado y contenido largo multipagina.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 18:22:02 -06:00
urieljarethandClaude Opus 5 45f166c116 Propuesta IA: reglas de validacion R0-R6 y filtro de contenido
R0 existe porque ninguna otra regla la cubre: las notas internas del asesor son
una ENTRADA que el modelo recibe en el prompt y puede copiar literalmente.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 18:17:30 -06:00
urieljarethandClaude Opus 5 7f375b3564 Propuesta IA: prompts en capas, capa 1 cacheable y sin fugas de dinero
Verificado que ni el precio ni el cuid de ServicioCotizado aparecen en ningun
prompt: el modelo solo ve refPartida. P1 deja de depender de una validacion.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 18:15:49 -06:00
urieljarethandClaude Opus 5 877a181a03 Propuesta IA: cliente MiniMax con tool-calling y reintentos
El contrato se fuerza con el input_schema de la herramienta porque MiniMax no
soporta structured outputs. tool_choice no se envia: fijarlo solo en el reintento
le daria un prefijo distinto y se pagaria el contexto completo.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 18:14:02 -06:00
urieljarethandClaude Opus 5 5bad273977 Propuesta IA: schemas Zod de los tres pasos, sin campos de dinero de E3
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 18:12:54 -06:00
urieljarethandClaude Opus 5 4e103beb27 Propuesta IA: modelo PropuestaIA y capa de datos economicos
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]>
2026-07-28 18:10:51 -06:00
urieljarethandClaude Opus 5 20f535d485 Retirar el servidor MCP y fijar todas las dependencias del API
MCP: se retira por completo (endpoint, paquete api/app/mcp/ y dependencia).
Tres razones, en orden de peso:

- Nadie lo usa.
- Llevaba roto desde antes de este trabajo. Con credencial valida devolvia 500:
  el handler construia un StreamableHTTPServerTransport nuevo por peticion, sin
  manejo de sesion. Lo que estaba expuesto a internet era la puerta abierta de un
  cuarto averiado.
- Su SDK sin fijar tumbo el API entero en produccion al saltar a 2.0.0.

Retirarlo es una mitigacion mas fuerte que autenticarlo, que fue lo que hizo el
commit anterior. Recuperable con `git show edb500f5:api/app/mcp/server.py`.
Se limpia tambien la ruta "/mcp" que el endpoint raiz seguia anunciando, y las
menciones a MCP del docstring y la descripcion de Swagger.

Dependencias: once de las doce eran rangos `>=` sin techo, o sea que cada
reconstruccion era una tirada de dados contra PyPI. `pydantic>=2.0` habria
aceptado pydantic 3 con la misma alegria con la que `mcp>=1.0.0` acepto 2.0.0.
Ahora todas van fijadas a la version exacta que corre sana en produccion,
capturada con pip freeze del contenedor healthy.

El lado Next.js ya era reproducible via package-lock.json; por eso el web nunca
se cayo durante el incidente y el api si.

Docs actualizados: AGENTS.md, README.md, api/COTIZADOR_API_SKILL.md y el spec,
que ademas registra en su seccion 0 las tres desviaciones de Fase 0 respecto a
lo disenado (el Despliegue B cancelado, la retirada del MCP y la deriva de
dependencia).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 17:51:21 -06:00
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