# check=skip=SecretsUsedInArgOrEnv

# La directiva de arriba tiene que ser la primera línea del archivo. Silencia la
# advertencia de BuildKit por el AUTH_SECRET de la etapa de construcción, que es
# un relleno para que `next build` no falle: no llega a la imagen final ni se
# usa en ejecución. Sin esto, cada construcción sale con una advertencia y se
# acaba aprendiendo a ignorar también las advertencias de verdad.

# =============================================================================
# FSPM — imagen de producción
#
# Tres etapas sobre node:22-alpine, aprovechando output: "standalone" de
# next.config.ts. La idea de cada etapa:
#
#   deps    instala las dependencias en su propia capa, así un cambio de código
#           no vuelve a bajar node_modules.
#   build   genera el cliente de Prisma, hornea una base de demostración y
#           compila Next.
#   runner  imagen mínima que solo sabe arrancar el servidor, con usuario no
#           root y la base de datos en un volumen.
#
# OJO CON LAS VARIABLES NEXT_PUBLIC_*
# Next las sustituye literalmente durante `next build`, no las lee al arrancar.
# Por eso NEXT_PUBLIC_SITE_URL y NEXT_PUBLIC_WHATSAPP van como ARG de
# construcción: cambiar el dominio exige reconstruir la imagen, no basta con
# editar el entorno del contenedor. Está explicado en docs/DESPLIEGUE.md.
# =============================================================================


# --- etapa 1: dependencias -----------------------------------------------------
FROM node:22-alpine AS deps

# libc6-compat y openssl: Prisma los necesita en Alpine para sus motores.
RUN apk add --no-cache libc6-compat openssl

WORKDIR /app

# El esquema entra antes de npm ci porque el postinstall de @prisma/client
# intenta generar el cliente y sin esquema deja una advertencia.
COPY package.json package-lock.json ./
COPY prisma/schema.prisma ./prisma/schema.prisma

# Aquí NO se define NODE_ENV=production: hacen falta las dependencias de
# desarrollo (prisma, tsx, typescript, tailwind) para construir y para sembrar.
RUN npm ci

# CLI de Prisma aislada, con sus dependencias propias resueltas en su propio
# árbol. El punto de entrada del contenedor la necesita para aplicar el esquema
# al arrancar, y copiar paquetes suelto por suelto no funciona: la CLI arrastra
# dependencias transitivas (effect, entre otras) que no son evidentes.
# La versión se lee de package.json, así que no puede desincronizarse.
#
# Después de instalarla se le quitan dos cosas que no va a usar nunca: los
# mapas de código fuente y el motor de consultas. Esta CLI solo ejecuta
# `db push`, que usa el motor de esquema; el motor de consultas que la
# aplicación necesita es el que viene dentro de la salida standalone. Son unos
# 40 MB menos en la imagen final.
RUN npm install --no-save --no-audit --no-fund --prefix /prisma-cli \
      "prisma@$(node -p "require('/app/package.json').devDependencies.prisma")" \
    && find /prisma-cli/node_modules -type f -name "*.map" -delete \
    && rm -f /prisma-cli/node_modules/@prisma/engines/libquery_engine-*.so.node


# --- etapa 2: construcción -----------------------------------------------------
FROM node:22-alpine AS build

RUN apk add --no-cache libc6-compat openssl

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Valores por omisión iguales a .env.example: si nadie pasa --build-arg, la
# imagen queda coherente en vez de quedar con la cadena vacía, que rompería
# `new URL(SITIO_URL)` en el layout raíz.
ARG NEXT_PUBLIC_SITE_URL="https://fspm-demo.urieljareth.org"
ARG NEXT_PUBLIC_WHATSAPP="525540348395"
ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
ENV NEXT_PUBLIC_WHATSAPP=${NEXT_PUBLIC_WHATSAPP}

ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

# `next build` y `prisma generate` exigen que DATABASE_URL exista, aunque no se
# conecten a la base real. Este valor es solo de compilación: apunta a la base
# de demostración que se hornea aquí mismo y nunca se usa en ejecución.
ENV DATABASE_URL="file:/semilla/fspm.db"
# Igual con el secreto de sesión: existe para que el build no falle. El
# contenedor recibe el suyo por entorno.
ENV AUTH_SECRET="secreto-de-compilacion-sin-valor-en-ejecucion"

RUN npx prisma generate

# --- base de demostración horneada en la imagen ---
#
# El seed es DESTRUCTIVO: `prisma/seed.ts` hace deleteMany() de todas las tablas
# antes de reconstruir. Ejecutarlo dentro del contenedor de producción borraría
# los leads capturados en cada reinicio. Por eso se ejecuta aquí, una sola vez,
# contra una base que queda dentro de la imagen; el contenedor solo la copia al
# volumen cuando el volumen está vacío. Ese es el motivo real por el que `tsx`,
# que es dependencia de desarrollo, nunca se instala en la imagen final.
RUN mkdir -p /semilla \
    && npx prisma db push --skip-generate \
    && if [ -f prisma/seed.ts ]; then \
         npx tsx prisma/seed.ts; \
       else \
         echo "AVISO: no existe prisma/seed.ts. La imagen queda con el esquema pero sin datos de demostración."; \
       fi

RUN npm run build


# --- etapa 3: ejecución --------------------------------------------------------
FROM node:22-alpine AS runner

# openssl y libc6-compat también aquí: el cliente de Prisma los carga en
# ejecución, no solo al construir.
RUN apk add --no-cache libc6-compat openssl

WORKDIR /app

ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
ENV NEXT_TELEMETRY_DISABLED=1
# El aviso de "hay una versión nueva de Prisma" sale en cada arranque y solo
# entrena a nadie a leer los registros. La versión se actualiza cuando el
# proyecto lo decida, no cuando el contenedor lo sugiera.
ENV PRISMA_HIDE_UPDATE_MESSAGE=true

# Ruta absoluta a propósito. Una relativa como "file:../data/fspm.db" se
# resuelve contra la carpeta del esquema y es justo el tipo de detalle que
# funciona en local y falla en el contenedor.
ENV DATABASE_URL="file:/app/data/fspm.db"

RUN addgroup --system --gid 1001 nodejs \
    && adduser --system --uid 1001 --ingroup nodejs nextjs

# CLI de Prisma, en su propio árbol para que el punto de entrada pueda aplicar
# el esquema al arrancar. Va en /app/prisma-cli, aparte del node_modules de la
# aplicación: así no puede pisar el @prisma/client generado que trae la salida
# standalone, que es el que la aplicación usa en ejecución.
COPY --from=deps /prisma-cli/node_modules ./prisma-cli/node_modules

# El esquema tiene que estar presente en ejecución: `prisma db push` lo lee.
COPY --from=build /app/prisma/schema.prisma ./prisma/schema.prisma

# Base de demostración horneada. Solo se copia al volumen si el volumen no
# tiene una base todavía.
COPY --from=build /semilla/fspm.db ./semilla/fspm.db

# Salida de Next. El standalone va al final para que su @prisma/client, que es
# el que la aplicación usa, gane sobre cualquier otro.
COPY --from=build /app/public ./public
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static

COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# El directorio de datos nace con dueño nextjs: si el volumen se crea vacío,
# Docker hereda estos permisos y el proceso no root puede escribir la base.
RUN mkdir -p /app/data \
    && chown -R nextjs:nodejs /app/data /app/semilla /app/.next

USER nextjs

# Aquí vive el SQLite y TIENE que sobrevivir a los redespliegues. Si no se
# monta, cada nueva versión de la imagen empieza con la base de demostración y
# los leads capturados se pierden. Está explicado en docs/DESPLIEGUE.md.
VOLUME ["/app/data"]

EXPOSE 3000

# La sonda toca /api/salud, que a su vez consulta la base: así un volumen no
# escribible se reporta como contenedor enfermo en vez de pasar por sano.
# --start-period cubre el `prisma db push` del arranque más el boot de Next.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
  CMD wget --quiet --tries=1 --spider "http://127.0.0.1:${PORT}/api/salud" || exit 1

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["node", "server.js"]
