# ✉️ Guía de Implementación: Sistema de Correos Institucionales, Validación por CURP (Llave MX), Enrutamiento por Estatus y Rediseño de Conclusión
### Sistema de Convocatorias IMCINE / FOCINE

Esta guía documenta detalladamente la arquitectura técnica, código fuente, archivos modificados y el procedimiento paso a paso para replicar en **todas las 13 convocatorias de FOCINE/IMCINE**:
1. **Bandera de validación por CURP:** Envío del correo de bienvenida únicamente en nuevos registros; si la CURP ya existe en la base de datos, se sincronizan los datos y se accede directo **sin enviar correo repetido**.
2. **Lógica Canónica de Enrutamiento por Estatus:** Redirección precisa al iniciar sesión (Llave MX o Login tradicional) hacia la vista correspondiente (`datos.php`, `carga_doc.php`, `conclusion.php` o `acceso_denegado.php`) según los 11 estados normativos del proyecto.
3. **Guardianes de Ruta en Front-End:** Protección en `datos.php` y `carga_doc.php` para que proyectos concluidos o con dictamen definitivo no muestren formularios vacíos o bloqueados. Asimismo, se garantiza acceso y guardado fluido en estatus de observaciones (`EN_CORRECCION`).
4. **Rediseño Integral Multi-Estatus de `conclusion.php` (Gob.mx / Sisdai):** Tarjetas diferenciadas para Carga Concluida, Observaciones Solventadas, **Proyectos Seleccionados / Inscritos con Guía de 4 Pasos a Seguir**, Proyectos No Inscritos y Descalificados.
5. **Nuevo Motor de Plantillas de Correo (Gob.mx / IMCINE):** Diseño institucional uniforme (Guinda `#691C32`, Dorado `#BC955C`, Verde `#103923`, tipografía *Montserrat*).
6. **Hoja de estilos centralizada:** `/home/convocatoriasdev/public_html/api_imcine_v2/css_base/correosMX.css` y `acuseMX.css`.
7. **Rediseño integral del Correo de Observaciones:** Formato profesional en tarjetas doradas, filas alternadas, contador dinámico y guía para subsanar.

---

## 📑 Índice
1. [Resumen Técnico del Flujo de Registro y Enrutamiento](#1-resumen-técnico-del-flujo-de-registro-y-enrutamiento)
2. [Matriz de Enrutamiento según Estatus del Proyecto](#2-matriz-de-enrutamiento-según-estatus-del-proyecto)
3. [Arquitectura de Estilos Centralizada (`api_imcine_v2`)](#3-arquitectura-de-estilos-centralizada-api_imcine_v2)
4. [Componente 1: Capa de Base de Datos (`dao_sistema.php`)](#componente-1-capa-de-base-de-datos-dao_sistemaphp)
5. [Componente 2: Lógica de Control y Bandera por CURP (`control_llavemx.php`)](#componente-2-lógica-de-control-y-bandera-por-curp-control_llavemxphp)
6. [Componente 3: Lógica Canónica de Enrutamiento y Corrección de Formularios (`control_sistema.php`)](#componente-3-lógica-canónica-de-enrutamiento-y-corrección-de-formularios-control_sistemaphp)
7. [Componente 4: Guardianes de Ruta en Front-End (`datos.php` y `carga_doc.php`)](#componente-4-guardianes-de-ruta-en-front-end-datosphp-y-carga_docphp)
8. [Componente 5: Rediseño Integral Multi-Estatus de `conclusion.php`](#componente-5-rediseño-integral-multi-estatus-de-conclusionphp)
9. [Componente 6: Motor y Plantillas de Correo (`templates_correos.php`)](#componente-6-motor-y-plantillas-de-correo-templates_correosphp)
10. [Componente 7: Formato de Filas de Observaciones (`herramientas.php` y `control_convocatoria.php`)](#componente-7-formato-de-filas-de-observaciones)
11. [Componente 8: Rediseño Institucional del Acuse Oficial y Anexo PDF (`templates_documentos.php`)](#componente-8-rediseño-institucional-del-acuse-oficial-y-anexo-pdf-templates_documentosphp)
12. [Script de Replicación Masiva para las 13 Convocatorias](#12-script-de-replicación-masiva-para-las-13-convocatorias)
13. [Prompt Listo para Ejecutar la Replicación](#13-prompt-listo-para-ejecutar-la-replicación)

---

## 1. Resumen Técnico del Flujo de Registro y Enrutamiento

```
              [ Autenticación con Llave MX o Tradicional ]
                                   │
                                   ▼
                  Obtención de CURP, Nombre y Correo
                                   │
                                   ▼
                 ¿Existe CURP en tabla 'proyectos'?
                           /               \
                    SÍ    /                 \   NO
                         ▼                   ▼
            ┌────────────────────────┐  ┌────────────────────────┐
            │  USUARIO YA REGISTRADO │  │     REGISTRO NUEVO     │
            │  - No envía correo     │  │  - Inserta en tabla    │
            │  - Audita acción       │  │  - Envía correo oficial│
            │  - Sincroniza datos    │  │    de bienvenida       │
            └────────────────────────┘  └────────────────────────┘
                         │                           │
                         └─────────────┬─────────────┘
                                       ▼
                       ¿Estatus del Proyecto en BD?
                     ┌─────────────────┴─────────────────┐
                     ▼                                   ▼
        [ En Captura / Observaciones ]       [ Concluido o Dictaminado ]
        - SIN_FINALIZAR   ➔ datos.php        - CARGA_CONCLUIDA ➔ conclusion.php
        - CARGA_INCOMPLETA ➔ carga_doc.php   - CORREGIDO       ➔ conclusion.php
        - EN_CORRECCION   ➔ carga_doc.php    - COMPLETO (Sel)  ➔ conclusion.php
          (Con acceso libre a datos.php)     - NO_COMPLETO     ➔ conclusion.php
                                             - DESCALIFICADO   ➔ conclusion.php
                                             - CANCELADO       ➔ acceso_denegado.php
```

---

## 2. Matriz de Enrutamiento según Estatus del Proyecto

| ID | Constante PHP | Descripción del Estado | Vista Destino | Comportamiento en Front-End |
| :---: | :--- | :--- | :--- | :--- |
| **1** | `SIN_FINALIZAR` | Registro inicial en proceso | `datos.php?tab=1` *(o `carga_doc.php` si datos ya validados)* | Formulario de captura inicial abierto y editable. **Acuse bloqueado.** |
| **2** | `CARGA_INCOMPLETA_SIN_ACUSE` | Registro no concluido en tiempo | `acceso_denegado.php?motivo=carga_incompleta` | `acceso_permitido = 0`. Bloqueo inmediato de sesión y redirección. **Acuse bloqueado.** |
| **3** | `CARGA_CONCLUIDA` | Registro inicial concluido con éxito | `conclusion.php` | **Acuse Oficial de Registro** (Guinda `#691C32`, Sello SHA-1, Descarga PDF). Bloquea edición. |
| **4** | `EN_CORRECCION` | Documentos con observaciones emitidas | `carga_doc.php` *(con acceso a `datos.php`)* | Muestra observaciones y campos para solventar. Permite editar datos generales en `datos.php`. **Acuse bloqueado.** |
| **5** | `CORREGIDO` | Observaciones solventadas y concluidas | `conclusion.php` | **Anexo al Acuse de Registro** (Dorado `#BC955C`, Descarga PDF Anexo + Acuse inicial). |
| **6** | `COMPLETO` | **Inscrito / Seleccionado (1ra Vuelta)** | `conclusion.php` | **¡Proyecto Inscrito / Seleccionado!** (Verde `#103923`), **Guía de 4 Pasos a Seguir**, descarga de Acuse Oficial. |
| **7** | `NO_COMPLETO` | **No Inscrito / No Seleccionado (1ra Vuelta)** | `conclusion.php` | **Proyecto No Inscrito (1ra Vuelta)**. Notificación oficial y descarga de constancia/acuse inicial. |
| **8** | `DESCALIFICADO` | Descalificado por observaciones no atendidas | `conclusion.php` | **Proyecto Descalificado**. Notificación formal de baja administrativa y constancia. |
| **9** | `COMPLETO_2DA` | **Inscrito / Seleccionado (2da Vuelta)** | `conclusion.php` | **¡Proyecto Inscrito / Seleccionado (2da Vuelta)!**, Pasos a Seguir, descarga de Anexo y Acuse. |
| **10** | `NO_COMPLETO_2DA` | **No Inscrito / No Seleccionado (2da Vuelta)** | `conclusion.php` | **Proyecto No Inscrito (Segunda Vuelta)**. Notificación oficial. |
| **98** | `CANCELADO` | Proyecto cancelado / inhabilitado | `acceso_denegado.php?motivo=cancelado` | Bloqueo inmediato de sesión y redirección (`acceso_permitido = 0`). |

### 2.1 Matriz de Dinamización de Acuses y Constancias en PDF (`TemplatesDocumentos`)

| Estatus ID | Constante PHP | Título del Banner en PDF | Insignia bajo Folio | Estatus del Expediente en Acuse | Color Institucional |
| :---: | :--- | :--- | :--- | :--- | :---: |
| **1** | `SIN_FINALIZAR` | *N/A* | *N/A* | *N/A* | ❌ **BLOQUEADO** (Redirige a captura) |
| **2** | `CARGA_INCOMPLETA_SIN_ACUSE` | *N/A* | *N/A* | *N/A* | ❌ **BLOQUEADO** (`acceso_permitido = 0`) |
| **4** | `EN_CORRECCION` | *N/A* | *N/A* | *N/A* | ❌ **BLOQUEADO** (Redirige a subsanación) |
| **3** | `CARGA_CONCLUIDA` | ACUSE OFICIAL DE CONCLUSIÓN DE REGISTRO | `[ REGISTRO CONCLUIDO ]` | **CARGA CONCLUIDA / EN ESPERA DE EVALUACIÓN** | `#103923` (Verde) |
| **5** | `CORREGIDO` | ACUSE OFICIAL DE REGISTRO - SUBSANACIÓN ATENDIDA | `[ OBSERVACIONES ATENDIDAS ]` | **OBSERVACIONES ATENDIDAS / EN ESPERA DE EVALUACIÓN** | `#8F6E3B` (Dorado) |
| **6** | `COMPLETO` | ACUSE OFICIAL - PROYECTO INSCRITO / SELECCIONADO | `[ PROYECTO SELECCIONADO ]` | **PROYECTO INSCRITO / SELECCIONADO (PRIMERA VUELTA)** | `#103923` (Verde) |
| **7** | `NO_COMPLETO` | CONSTANCIA OFICIAL DE DICTAMEN - PROYECTO NO INSCRITO | `[ NO SELECCIONADO ]` | **PROYECTO NO INSCRITO (PRIMERA VUELTA)** | `#4B4F54` (Gris) |
| **8** | `DESCALIFICADO` | CONSTANCIA OFICIAL DE PROYECTO DESCALIFICADO | `[ TRÁMITE DESCALIFICADO ]` | **PROYECTO DESCALIFICADO (NO SUBSANÓ OBSERVACIONES)** | `#691C32` (Guinda) |
| **9** | `COMPLETO_2DA` | ACUSE OFICIAL - PROYECTO SELECCIONADO (SEGUNDA VUELTA) | `[ PROYECTO SELECCIONADO 2DA VUELTA ]` | **PROYECTO INSCRITO / SELECCIONADO (SEGUNDA VUELTA)** | `#103923` (Verde) |
| **10** | `NO_COMPLETO_2DA` | CONSTANCIA OFICIAL DE DICTAMEN - SEGUNDA VUELTA | `[ NO SELECCIONADO 2DA VUELTA ]` | **PROYECTO NO INSCRITO (SEGUNDA VUELTA)** | `#4B4F54` (Gris) |
| **98** | `CANCELADO` | CONSTANCIA OFICIAL DE PROYECTO CANCELADO | `[ TRÁMITE CANCELADO ]` | **PROYECTO CANCELADO / INHABILITADO** | `#691C32` (Guinda) |

---

## 3. Arquitectura de Estilos Centralizada (`api_imcine_v2`)

Los estilos institucionales residen centralizados en:
- **Rutas en Servidor:**
  - `/home/convocatoriasdev/public_html/api_imcine_v2/css_base/correosMX.css` (Correos electrónicos)
  - `/home/convocatoriasdev/public_html/api_imcine_v2/css_base/acuseMX.css` (Acuse, Anexo y `conclusion.php`)
  - `/home/convocatoriasdev/public_html/api_imcine_v2/imagenes/cultura.jpg` (Logotipo oficial Cultura/IMCINE)
- **Paleta Normativa:** Guinda `#691C32`, Dorado `#BC955C`, Verde Bandera `#103923`, Fondo Gris `#F4F6F8`.

---

## 4. Componente 1: Capa de Base de Datos (`dao_sistema.php`)

**Ruta:** `backEnd/DAO/dao_sistema.php`

Asegurar al final de la clase `ClaseDaoSistema` los métodos de búsqueda estricta por CURP:

```php
    // =========================================================================
    // MÉTODOS DE VALIDACIÓN POR CURP - INTEGRACIÓN LLAVE MX Y CORREOS
    // =========================================================================

    /**
     * Busca un proyecto existente estrictamente por CURP (excluyendo cancelados estatus 98)
     */
    function buscarProyectoPorCurp($curp = "") {
        $conexion = new OBD();
        $tools = new Herramientas();
        $curpLimpio = str_replace("'", "''", trim($curp));

        if (empty($curpLimpio)) {
            return array();
        }

        $query = "SELECT * FROM proyectos WHERE curp = '" . $curpLimpio . "' AND estatus_proyecto != 98 ORDER BY id_proyecto DESC LIMIT 1";
        $rs = $conexion->query_select($query, DSN_LOCAL);
        if (!empty($rs)) {
            $rs = $tools->codificarUTF8($rs);
        }
        return $rs;
    }

    /**
     * Retorna true si la CURP ya está registrada en un proyecto activo de la convocatoria
     */
    function existeCurpProyecto($curp = "") {
        $res = $this->buscarProyectoPorCurp($curp);
        return (!empty($res) && count($res) > 0 && !empty($res[0]["id_proyecto"]));
    }
```

---

## 5. Componente 2: Lógica de Control y Bandera por CURP (`control_llavemx.php`)

**Ruta:** `backEnd/CONTROL/control_llavemx.php`

En la cabecera, requerir `control_sistema.php`:
```php
require_once __DIR__ . DIRECTORY_SEPARATOR . "control_sistema.php";
```

En `procesarInicioSesion()`, validar CURP, omitir correo a usuarios existentes y utilizar la ruta canónica:

```php
        // 5. Validar estrictamente por CURP si el usuario ya existe en la convocatoria
        $dao = new ClaseDaoSistema();
        $proyectosPorCurp = !empty($curp) ? $dao->buscarProyectoPorCurp($curp) : array();
        $proyectosExistentes = !empty($proyectosPorCurp) ? $proyectosPorCurp : $dao->buscarProyectoPorLlaveMX($curp, $rfcFisica, $rfcMoral, $correo);
        $esNuevoRegistro = empty($proyectosExistentes);

        $idProyecto     = 0;
        $visitantemd5   = '';
        $userName       = '';
        $statusProyecto = 1;
        $datosValidados = 0;

        if (!$esNuevoRegistro && !empty($proyectosExistentes) && count($proyectosExistentes) > 0) {
            // Usuario existente: actualizar datos verificados. NO se envía correo de registro.
            $idProyecto       = $proyectosExistentes[0]["id_proyecto"];
            $visitantemd5     = $proyectosExistentes[0]["visitantemd5"];
            $userName         = $proyectosExistentes[0]["user_name"];
            $statusProyecto   = intval($proyectosExistentes[0]["estatus_proyecto"]);
            $datosValidados   = isset($proyectosExistentes[0]["datos_validados"]) ? intval($proyectosExistentes[0]["datos_validados"]) : 0;
            $accesoPermitido  = isset($proyectosExistentes[0]["acceso_permitido"]) ? intval($proyectosExistentes[0]["acceso_permitido"]) : 1;
            $tipoPersona      = !empty($proyectosExistentes[0]["persona_fiscal"]) ? $proyectosExistentes[0]["persona_fiscal"] : $tipoPersona;

            $dao->actualizarDatosDesdeLlaveMX($idProyecto, $arregloSync);
            $this->auditarTransaccion('PROYECTO_ACTUALIZADO', array('id_proyecto' => $idProyecto, 'curp' => $curp));
            $this->auditarTransaccion('LOGIN_USUARIO_EXISTENTE_SIN_CORREO', array(
                'id_proyecto' => $idProyecto,
                'curp'        => $curp,
                'correo'      => $correo,
                'motivo'      => 'CURP ya registrada previamente en base de datos. Se omite envío de correo de bienvenida.'
            ));

            // Validar bloqueo (inhabilitado o cancelado)
            if ($accesoPermitido === 0 || $statusProyecto === 98) {
                $motivoBloqueo = ($statusProyecto === 98) ? 'cancelado' : 'inhabilitado';
                return array('success' => true, 'redirectUrl' => 'acceso_denegado.php?motivo=' . $motivoBloqueo, 'acceso_denegado' => true);
            }
        } else {
            // Nuevo Registro: registrar y enviar correo oficial
            $reg = $dao->registrarProyectoDesdeLlaveMX($arregloSync);
            if (!empty($reg["id"])) {
                $idProyecto     = $reg["id"];
                $visitantemd5   = $reg["visitantemd5"];
                $userName       = $arregloSync["user_name"];
                $statusProyecto = 1;
                $datosValidados = 0;
                $this->auditarTransaccion('PROYECTO_CREADO_LLAVEMX', array('id_proyecto' => $idProyecto, 'curp' => $curp));

                // DISPARAR CORREO DE BIENVENIDA SOLO PARA NUEVO REGISTRO
                $this->enviarCorreoBienvenidaLlaveMX($datosLmx, $idProyecto, $arregloSync);
            } else {
                return array('success' => false, 'error' => 'Error al guardar los datos del usuario.');
            }
        }

        // ... Configuración de sesión ...

        // Determinar ruta de redirección canónica según el estatus del proyecto
        $ctrlSys = new ClaseControlSistema();
        $redirectUrl = $ctrlSys->obtenerRutaPorEstatus($statusProyecto, $datosValidados, $nombre);

        return array(
            'success'     => true,
            'redirectUrl' => $redirectUrl,
            'id_proyecto' => $idProyecto,
            'usuario'     => $userName
        );
```

---

## 6. Componente 3: Lógica Canónica de Enrutamiento y Corrección de Formularios (`control_sistema.php`)

**Ruta:** `backEnd/CONTROL/control_sistema.php`

### A. Método `obtenerRutaPorEstatus`:
Añadir después de `validarLogin()`:

```php
	/**
	 * Determina de forma canónica la ruta de redirección según el estatus del proyecto
	 * y el avance en la captura de datos.
	 */
	function obtenerRutaPorEstatus($statusProyecto, $datosValidados = 0, $usuario = "") {
		$status = intval($statusProyecto);
		$validados = intval($datosValidados);
		$saludo = !empty($usuario) ? "Hola " . $usuario : "Bienvenido(a)";

		switch ($status) {
			case SIN_FINALIZAR: // 1
				if ($validados === 1) {
					return "carga_doc.php?mensaje=" . urlencode("Continúa con la carga de documentos.");
				}
				return "datos.php?tab=1&mensaje=" . urlencode($saludo);

			case CARGA_INCOMPLETA_SIN_ACUSE: // 2
				return "carga_doc.php?mensaje=" . urlencode("Completa tu información y carga de documentos.");

			case EN_CORRECCION: // 4
				return "carga_doc.php?mensaje=" . urlencode("Tu proyecto tiene observaciones que deben ser solventadas.");

			case CARGA_CONCLUIDA: // 3
			case CORREGIDO:       // 5
			case COMPLETO:        // 6
			case NO_COMPLETO:     // 7
			case DESCALIFICADO:   // 8
			case COMPLETO_2DA:    // 9
			case NO_COMPLETO_2DA: // 10
				return "conclusion.php?mensaje=" . urlencode("Estado actual de tu proyecto.");

			case CANCELADO: // 98
				return "acceso_denegado.php?motivo=cancelado";

			default:
				return "datos.php?tab=1";
		}
	}
```

### B. Actualización de `validarLogin()`:
En `validarLogin()`, reemplazar la bifurcación manual por:
```php
		$datos_validados = isset($usuario[0]["datos_validados"]) ? intval($usuario[0]["datos_validados"]) : 0;
		$respuesta = $this->obtenerRutaPorEstatus($status_proyecto, $datos_validados, $arreglo["usuario"]);
```

### C. Corrección en `mostrarOcultarForm()` y `habilitarDeshabilitarBTNGuardar()`:
Garantizar que en `EN_CORRECCION` el formulario de datos y el botón de guardar sean visibles:

```php
	function mostrarOcultarForm($entrada, $estatus_proyecto) {
		$dao = new ClaseDaoSistema();
		$si = $dao->seguro($entrada);
		$estatus = intval($estatus_proyecto);

		if (($si >= 1 || !empty($_SESSION['usu'])) && ($estatus == SIN_FINALIZAR || $estatus == CARGA_INCOMPLETA_SIN_ACUSE || $estatus == EN_CORRECCION))
			echo "display:";    
		else   
			echo "display:none";				
	}

	function habilitarDeshabilitarBTNGuardar($estatus_proyecto, $datos_validados) {
		$estatus = intval($estatus_proyecto);
		if ($estatus == SIN_FINALIZAR || $estatus == CARGA_INCOMPLETA_SIN_ACUSE || $estatus == EN_CORRECCION) 
			echo "display:";    
		else   
			echo "display:none";	
	}
```

### D. Rutas Portables para PDF (evitar hardcoded paths):
En `guardarTextoComoPDF()`:
```php
		$ruta_base = getcwd() . DIRECTORY_SEPARATOR . "files" . DIRECTORY_SEPARATOR;
		require_once __DIR__ . DIRECTORY_SEPARATOR . "FPDF" . DIRECTORY_SEPARATOR . "fpdf.php";
```

---

## 7. Componente 4: Guardianes de Ruta en Front-End (`datos.php` y `carga_doc.php`)

Para evitar que usuarios que concluyeron su trámite o tienen dictamen ingresen por URL directa a formularios vacíos o ineditables:

### A. Guardián en `datos.php`:
Al inicio de `datos.php` (antes de `include("header_session.php")`):

```php
<?php
require_once 'backEnd/init_session.php';

// Guardián de ruta: Postulantes que ya concluyeron o tienen dictamen no deben acceder a datos.php
if (@$_SESSION['perfil'] == POSTULANTE) {
	$estatusActual = intval($estatus_proyecto);

	// Estatus concluidos o terminales -> conclusion.php
	if (in_array($estatusActual, array(CARGA_CONCLUIDA, CORREGIDO, COMPLETO, NO_COMPLETO, DESCALIFICADO, COMPLETO_2DA, NO_COMPLETO_2DA, CANCELADO))) {
		header("Location: conclusion.php");
		exit();
	}
}
include ("header_session.php");
?>
```

### B. Guardián en `carga_doc.php`:
Al inicio de `carga_doc.php`:

```php
<?php
require_once 'backEnd/init_session.php';

// Guardián de ruta en carga_doc.php: Postulantes concluidos van a conclusion.php
if (@$_SESSION['perfil'] == POSTULANTE) {
	$estatusActual = intval($estatus_proyecto);

	// Estatus concluidos o terminales -> conclusion.php
	if (in_array($estatusActual, array(CARGA_CONCLUIDA, CORREGIDO, COMPLETO, NO_COMPLETO, DESCALIFICADO, COMPLETO_2DA, NO_COMPLETO_2DA, CANCELADO))) {
		header("Location: conclusion.php");
		exit();
	}
}
include ("header_session.php");
?>
```

---

## 8. Componente 5: Rediseño Integral Multi-Estatus de `conclusion.php`

**Ruta:** `conclusion.php`

El archivo `conclusion.php` implementa una vista modular institucional de acuerdo con los lineamientos del Gobierno de México (Sisdai / SND):

1. **Guardián Inicial:** Redirige a captura a usuarios en `SIN_FINALIZAR` o `EN_CORRECCION`.
2. **Ficha Resumen del Proyecto:** Folio oficial (`FOCINE-00001`), título del proyecto, convocatoria e insignia dinámica con el estatus.
3. **Módulo 1: Carga Concluida Inicial (`CARGA_CONCLUIDA = 3`):**
   - Encabezado: *"¡Carga Concluida Exitosamente!"*.
   - Tarjeta con borde guinda `#691C32`, sello criptográfico SHA-1 y botón para descargar el **Acuse Oficial en PDF**.
4. **Módulo 2: Observaciones Solventadas (`CORREGIDO = 5`):**
   - Encabezado: *"¡Observaciones Solventadas Exitosamente!"*.
   - Tarjeta con borde dorado `#BC955C` y botón para descargar el **Anexo al Acuse en PDF**, más enlace para descargar el Acuse inicial.
5. **Módulo 3: Proyecto Seleccionado / Inscrito (`COMPLETO = 6` o `COMPLETO_2DA = 9`):**
   - Encabezado verde `#103923` con estrella de selección: *"¡Proyecto Inscrito / Seleccionado!"*.
   - Mensaje oficial de felicitación institucional del IMCINE.
   - **Guía Visual de 4 Pasos a Seguir:**
     * **Paso 1:** Notificación Oficial por Correo Electrónico.
     * **Paso 2:** Formalización del Convenio de Apoyo FOCINE.
     * **Paso 3:** Cotejo Documental del Expediente.
     * **Paso 4:** Resguardo de Comprobantes Digitales.
   - Botones para descargar Acuse Oficial en PDF (y Anexo si es 2da vuelta).
6. **Módulo 4: Proyecto No Inscrito (`NO_COMPLETO = 7` o `NO_COMPLETO_2DA = 10`):**
   - Encabezado institucional: *"Resultado de Dictamen: Proyecto No Inscrito"*.
   - Mensaje formal informando el dictamen del Comité Técnico, enlace a actas en `www.imcine.gob.mx` y botón de descarga de comprobante histórico de registro.
7. **Módulo 5: Proyecto Descalificado (`DESCALIFICADO = 8`):**
   - Aviso administrativo formal de baja por observaciones no solventadas en plazo normativo.

*(Copiar el archivo completo de `/home/convocatoriasdev/public_html/focine/ficcion_oscar/conclusion.php` directamente).*

---

## 9. Componente 6: Motor y Plantillas de Correo (`templates_correos.php`)

**Ruta:** `backEnd/TEMPLATES/templates_correos.php`

Contiene `render_correo_base()`, `template_registro_llavemx()`, `template_correo_usuario()` y `template_observaciones()`.
*(Copiar directamente desde `ficcion_oscar/backEnd/TEMPLATES/templates_correos.php`).*

---

## 10. Componente 7: Formato de Filas de Observaciones

Tanto en `backEnd/utilerias/herramientas.php` como en `backEnd/CONTROL/control_convocatoria.php`, el método `filaObservacion(...)` genera filas estilizadas en `#FAF8F5` y `#FFFFFF`, sin texto justificado, y `fechaCompelta()` formatea en español con zona horaria de México.

---

## 11. Componente 8: Rediseño Institucional del Acuse Oficial y Anexo PDF (`templates_documentos.php`)

**Ruta:** `backEnd/TEMPLATES/templates_documentos.php`

Genera los formatos PDF institucionales del Acuse Oficial y Anexo de Subsanación con pleca tricolor, logos de Cultura e IMCINE, sellos digitales y estructura por tablas.
*(Copiar directamente desde `ficcion_oscar/backEnd/TEMPLATES/templates_documentos.php`).*

---

## 12. Script de Replicación Masiva para las 13 Convocatorias

Ejecutar el siguiente script en la terminal Linux del servidor para replicar de forma automatizada y segura todos los archivos y modificaciones a las 13 convocatorias:

```bash
#!/bin/bash
# ==============================================================================
# SCRIPT DE REPLICACIÓN INTEGRAL: CORREOS, ACUSES, ENRUTAMIENTO Y VISTAS MODULARES
# Convocatorias FOCINE / IMCINE (13 Convocatorias)
# ==============================================================================

CONVOCATORIAS=(
  "acervos"
  "animacion"
  "cortometraje"
  "documental"
  "estados"
  "exhibicion_equipamientos"
  "exhibicion_programas"
  "experimental"
  "ficcion"
  "infancias"
  "infancias_2027"
  "operasprimas"
  "postproduccion"
)

BASE_DIR="/home/convocatoriasdev/public_html/focine"
ORIGEN="$BASE_DIR/ficcion_oscar"

echo "=== Iniciando replicación masiva desde ficcion_oscar ==="

# 1. Asegurar hojas de estilo centralizadas y logo cultura.jpg en api_imcine_v2
cp -f "$ORIGEN/correosMX.css" /home/convocatoriasdev/public_html/api_imcine_v2/css_base/correosMX.css
cp -f "$ORIGEN/acuseMX.css" /home/convocatoriasdev/public_html/api_imcine_v2/css_base/acuseMX.css
chmod 644 /home/convocatoriasdev/public_html/api_imcine_v2/css_base/correosMX.css
chmod 644 /home/convocatoriasdev/public_html/api_imcine_v2/css_base/acuseMX.css
cp -f /home/convocatoriasdev/public_html/api_imcine_v2/imagenes/cultura_2026.jpg /home/convocatoriasdev/public_html/api_imcine_v2/imagenes/cultura.jpg
echo "✔ Hojas correosMX.css, acuseMX.css e imagen cultura.jpg aseguradas en api_imcine_v2"

for conv in "${CONVOCATORIAS[@]}"; do
  DEST="$BASE_DIR/$conv"
  echo "------------------------------------------------------"
  echo "Procesando convocatoria: $conv"

  if [ ! -d "$DEST" ]; then
    echo "⚠ Directorio $DEST no encontrado, omitiendo..."
    continue
  fi

  # A. Sincronizar plantillas de correos y documentos PDF
  cp -f "$ORIGEN/backEnd/TEMPLATES/templates_correos.php" "$DEST/backEnd/TEMPLATES/templates_correos.php"
  cp -f "$ORIGEN/backEnd/TEMPLATES/templates_documentos.php" "$DEST/backEnd/TEMPLATES/templates_documentos.php"
  echo "  ✔ templates_correos.php y templates_documentos.php copiados"

  # B. Sincronizar vista de conclusión modular y header_session
  cp -f "$ORIGEN/conclusion.php" "$DEST/conclusion.php"
  cp -f "$ORIGEN/header_session.php" "$DEST/header_session.php"
  echo "  ✔ conclusion.php (multi-estatus) y header_session.php copiados"

  # C. Sincronizar controladores y utilerías
  cp -f "$ORIGEN/backEnd/CONTROL/control_llavemx.php" "$DEST/backEnd/CONTROL/control_llavemx.php"
  cp -f "$ORIGEN/backEnd/CONTROL/control_sistema.php" "$DEST/backEnd/CONTROL/control_sistema.php"
  cp -f "$ORIGEN/backEnd/utilerias/herramientas.php" "$DEST/backEnd/utilerias/herramientas.php"
  echo "  ✔ control_llavemx.php, control_sistema.php y herramientas.php copiados"

  # D. Inyectar guardián de ruta en datos.php si no existe
  python3 -c "
path = '$DEST/datos.php'
try:
    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
        content = f.read()

    guard = '''<?php
require_once 'backEnd/init_session.php';

// Guardián de ruta: Postulantes que ya concluyeron o tienen dictamen no deben acceder a datos.php
if (@\$_SESSION['perfil'] == POSTULANTE) {
\t\$estatusActual = intval(\$estatus_proyecto);

\t// Estatus concluidos o terminales -> conclusion.php
\tif (in_array(\$estatusActual, array(CARGA_CONCLUIDA, CORREGIDO, COMPLETO, NO_COMPLETO, DESCALIFICADO, COMPLETO_2DA, NO_COMPLETO_2DA, CANCELADO))) {
\t\theader(\"Location: conclusion.php\");
\t\texit();
\t}
}
include (\"header_session.php\");
?>'''

    if 'Guardián de ruta' not in content:
        # Reemplazar la inclusión inicial de header_session
        import re
        new_content = re.sub(r'<\?php\s+include\s*\(\s*[\'\"]header_session\.php[\'\"]\s*\);\s*\?>', guard, content, count=1)
        if new_content != content:
            with open(path, 'w', encoding='utf-8') as f:
                f.write(new_content)
            print('  ✔ Guardián de ruta inyectado en datos.php')
        else:
            print('  ℹ No se pudo reemplazar header_session en datos.php de forma automática')
    else:
        print('  ℹ datos.php ya cuenta con guardián de ruta')
except Exception as e:
    print('  ⚠ Error al procesar datos.php:', e)
"

  # E. Inyectar guardián de ruta en carga_doc.php si no existe
  python3 -c "
path = '$DEST/carga_doc.php'
try:
    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
        content = f.read()

    guard = '''<?php
require_once 'backEnd/init_session.php';

// Guardián de ruta en carga_doc.php: Postulantes concluidos van a conclusion.php
if (@\$_SESSION['perfil'] == POSTULANTE) {
\t\$estatusActual = intval(\$estatus_proyecto);

\t// Estatus concluidos o terminales -> conclusion.php
\tif (in_array(\$estatusActual, array(CARGA_CONCLUIDA, CORREGIDO, COMPLETO, NO_COMPLETO, DESCALIFICADO, COMPLETO_2DA, NO_COMPLETO_2DA, CANCELADO))) {
\t\theader(\"Location: conclusion.php\");
\t\texit();
\t}
}
include (\"header_session.php\");
?>'''

    if 'Guardián de ruta' not in content:
        import re
        new_content = re.sub(r'<\?php\s+include\s*\(\s*[\'\"]header_session\.php[\'\"]\s*\);\s*\?>', guard, content, count=1)
        if new_content != content:
            with open(path, 'w', encoding='utf-8') as f:
                f.write(new_content)
            print('  ✔ Guardián de ruta inyectado en carga_doc.php')
        else:
            print('  ℹ No se pudo reemplazar header_session en carga_doc.php de forma automática')
    else:
        print('  ℹ carga_doc.php ya cuenta con guardián de ruta')
except Exception as e:
    print('  ⚠ Error al procesar carga_doc.php:', e)
"

  # F. Inyectar buscarProyectoPorCurp en dao_sistema.php si no existe
  if ! grep -q "buscarProyectoPorCurp" "$DEST/backEnd/DAO/dao_sistema.php"; then
    python3 -c "
path = '$DEST/backEnd/DAO/dao_sistema.php'
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
    content = f.read()

metodos = '''
\t// =========================================================================
\t// MÉTODOS DE VALIDACIÓN POR CURP - INTEGRACIÓN LLAVE MX Y CORREOS
\t// =========================================================================
\tfunction buscarProyectoPorCurp(\$curp = \"\") {
\t\t\$conexion = new OBD();
\t\t\$tools = new Herramientas();
\t\t\$curpLimpio = str_replace(\"'\", \"''\", trim(\$curp));
\t\tif (empty(\$curpLimpio)) return array();
\t\t\$query = \"SELECT * FROM proyectos WHERE curp = '\" . \$curpLimpio . \"' AND estatus_proyecto != 98 ORDER BY id_proyecto DESC LIMIT 1\";
\t\t\$rs = \$conexion->query_select(\$query, DSN_LOCAL);
\t\tif (!empty(\$rs)) \$rs = \$tools->codificarUTF8(\$rs);
\t\treturn \$rs;
\t}

\tfunction existeCurpProyecto(\$curp = \"\") {
\t\t\$res = \$this->buscarProyectoPorCurp(\$curp);
\t\treturn (!empty(\$res) && count(\$res) > 0 && !empty(\$res[0][\"id_proyecto\"]));
\t}
'''
idx = content.rfind('}')
if idx != -1:
    new_content = content[:idx] + metodos + '\n}\n'
    with open(path, 'w', encoding='utf-8') as f:
        f.write(new_content)
    print('  ✔ Métodos de CURP inyectados en dao_sistema.php')
"
  else
    echo "  ℹ dao_sistema.php ya contiene buscarProyectoPorCurp"
  fi

  # G. Validar sintaxis PHP
  php -l "$DEST/backEnd/DAO/dao_sistema.php" >/dev/null && \
  php -l "$DEST/backEnd/CONTROL/control_llavemx.php" >/dev/null && \
  php -l "$DEST/backEnd/CONTROL/control_sistema.php" >/dev/null && \
  php -l "$DEST/backEnd/TEMPLATES/templates_correos.php" >/dev/null && \
  php -l "$DEST/backEnd/TEMPLATES/templates_documentos.php" >/dev/null && \
  php -l "$DEST/conclusion.php" >/dev/null && \
  php -l "$DEST/datos.php" >/dev/null && \
  php -l "$DEST/carga_doc.php" >/dev/null && \
  php -l "$DEST/backEnd/utilerias/herramientas.php" >/dev/null

  if [ $? -eq 0 ]; then
    echo "  ✔ Sintaxis PHP validada con éxito en todos los archivos"
  else
    echo "  ❌ Error de sintaxis detectado en $conv"
  fi
done

echo "======================================================"
echo "✔ Replicación masiva completada con éxito en las 13 convocatorias."
```

---

## 13. Prompt Listo para Ejecutar la Replicación

Puedes copiar y pegar este prompt en tu agente de desarrollo para replicar los cambios de inmediato en cualquier entorno:

```markdown
# Prompt Maestro: Implementación Integral en las 13 Convocatorias de FOCINE 2026

Actúa como Ingeniero de Software Senior en PHP y desarrollo web institucional para el Gobierno de México / IMCINE.

## Contexto y Guía de Referencia
Debes implementar de forma integral el sistema de estatus dinámicos, acuses y constancias en PDF, integración con Llave MX (validación por CURP), plantillas institucionales de correo y vista modular multi-estatus en las **13 convocatorias de FOCINE**.

Para todos los detalles técnicos, código fuente y reglas de negocio, guíate estrictamente por:
- **Guía de Implementación:** `/home/convocatoriasdev/public_html/focine/ficcion_oscar/GUIA_IMPLEMENTACION_CORREOS_Y_LLAVEMX.md`
- **Modelo Base de Referencia:** `/home/convocatoriasdev/public_html/focine/ficcion_oscar/`

## Convocatorias Destino (13 en total)
Ruta base: `/home/convocatoriasdev/public_html/focine/`
1. `acervos`
2. `animacion`
3. `cortometraje`
4. `documental`
5. `estados`
6. `exhibicion_equipamientos`
7. `exhibicion_programas`
8. `experimental`
9. `ficcion`
10. `infancias`
11. `infancias_2027`
12. `operasprimas`
13. `postproduccion`

---

## Procedimiento de Implementación por Convocatoria

### Paso 1: Asegurar Activos Centralizados (`api_imcine_v2`)
Verifica que existan y cuenten con permisos de lectura (644):
- `/home/convocatoriasdev/public_html/api_imcine_v2/css_base/correosMX.css`
- `/home/convocatoriasdev/public_html/api_imcine_v2/css_base/acuseMX.css`
- `/home/convocatoriasdev/public_html/api_imcine_v2/imagenes/cultura.jpg`

### Paso 2: Archivos Portables a Copiar Directamente
Copia desde `/home/convocatoriasdev/public_html/focine/ficcion_oscar/` hacia cada convocatoria destino:
1. `conclusion.php` (Vista modular para 5 estatus, guía de 4 pasos para seleccionados y guardián de ruta).
2. `acceso_denegado.php` (Manejo de estados: inhabilitado, cancelado y carga_incompleta).
3. `header_session.php` (Cabecera dinámica con navegación condicional).
4. `login_llavemx.php` y `retorno_llavemx.php` (Flujo OAuth Llave MX).
5. `backEnd/init_session.php` (Control de sesión y bloqueo para postulantes cancelados, inhabilitados o con carga_incompleta).
6. `backEnd/CONTROL/control_convocatoria.php` (Asignación automática de `acceso_permitido = 0` en estatus 98 y 2).
7. `backEnd/CONTROL/control_llavemx.php` (Validación por CURP y enrutamiento canónico).
8. `backEnd/CONTROL/control_sistema.php` (Método `obtenerRutaPorEstatus()`, bloqueo de acuses en estados interactivos 1, 2 y 4, y rutas relativas portables con `getcwd()`).
9. `backEnd/TEMPLATES/templates_correos.php` (Motor de plantillas de correo).
10. `backEnd/TEMPLATES/templates_documentos.php` (Acuses oficiales y anexos con título, insignia y estatus del expediente dinámico).
11. `backEnd/utilerias/herramientas.php` (Formato de fecha en español y observaciones).

### Paso 3: Inyecciones Quirúrgicas (NUNCA SOBRESCRIBIR)

#### A. En `datos.php`:
No sobrescribas el formulario. Inyecta al inicio antes de incluir `header_session.php`:
```php
<?php
require_once 'backEnd/init_session.php';

// Guardián de ruta: Postulantes que ya concluyeron o tienen dictamen no deben acceder a datos.php
if (@$_SESSION['perfil'] == POSTULANTE) {
	$estatusActual = intval($estatus_proyecto);

	if (in_array($estatusActual, array(CARGA_CONCLUIDA, CORREGIDO, COMPLETO, NO_COMPLETO, DESCALIFICADO, COMPLETO_2DA, NO_COMPLETO_2DA, CANCELADO))) {
		header("Location: conclusion.php");
		exit();
	}
}
include ("header_session.php");
?>
```

#### B. En `carga_doc.php`:
No sobrescribas la tabla de documentos. Inyecta al inicio antes de incluir `header_session.php`:
```php
<?php
require_once 'backEnd/init_session.php';

// Guardián de ruta: Redirigir a conclusion.php si el proyecto ya concluyó su fase de carga o tiene dictamen
if (@$_SESSION['perfil'] == POSTULANTE) {
	$estatusActual = intval($estatus_proyecto);

	if (in_array($estatusActual, array(CARGA_CONCLUIDA, CORREGIDO, COMPLETO, NO_COMPLETO, DESCALIFICADO, COMPLETO_2DA, NO_COMPLETO_2DA, CANCELADO))) {
		header("Location: conclusion.php");
		exit();
	}
}
include ("header_session.php");
?>
```

#### C. En `backEnd/DAO/dao_sistema.php`:
Verifica si existen `buscarProyectoPorCurp` y `existeCurpProyecto`. Si no existen, inyéctalos al final de la clase antes de la llave de cierre `}`:
```php
	// =========================================================================
	// MÉTODOS DE VALIDACIÓN POR CURP - INTEGRACIÓN LLAVE MX Y CORREOS
	// =========================================================================
	function buscarProyectoPorCurp($curp = "") {
		$conexion = new OBD();
		$tools = new Herramientas();
		$curpLimpio = str_replace("'", "''", trim($curp));
		if (empty($curpLimpio)) return array();
		$query = "SELECT * FROM proyectos WHERE curp = '" . $curpLimpio . "' AND estatus_proyecto != 98 ORDER BY id_proyecto DESC LIMIT 1";
		$rs = $conexion->query_select($query, DSN_LOCAL);
		if (!empty($rs)) $rs = $tools->codificarUTF8($rs);
		return $rs;
	}

	function existeCurpProyecto($curp = "") {
		$res = $this->buscarProyectoPorCurp($curp);
		return (!empty($res) && count($res) > 0 && !empty($res[0]["id_proyecto"]));
	}
```

---

## Verificación Requerida
1. Ejecuta `php -l` en todos los archivos modificados de cada una de las 13 convocatorias para asegurar 0 errores de sintaxis:
   - `conclusion.php`
   - `acceso_denegado.php`
   - `datos.php`
   - `carga_doc.php`
   - `backEnd/init_session.php`
   - `backEnd/CONTROL/control_convocatoria.php`
   - `backEnd/CONTROL/control_sistema.php`
   - `backEnd/CONTROL/control_llavemx.php`
   - `backEnd/TEMPLATES/templates_documentos.php`
   - `backEnd/TEMPLATES/templates_correos.php`
   - `backEnd/DAO/dao_sistema.php`
2. Genera una tabla de estatus final certificando que las 13 convocatorias fueron procesadas e implementadas con éxito.
```

