# Feature Specification: Blindaje del Backend ante Datos Incompletos y Fuera de Rango

**Feature Branch**: `013-blindaje-backend-integridad-datos`

**Created**: 2026-09-18

**Status**: Draft

**Input**: Análisis de riesgo de "null pointer" en backend + análisis de validaciones frontend/backend/BD, ambos verificados línea por línea contra el código actual. Ver `INFORME_ANALISIS_NULLPOINTER_BACKEND.md`, `INFORME_ANALISIS_VALIDACIONES_FRONTEND.md` y `PLAN_BLINDAJE_BACKEND.md` en la raíz del repositorio.

> **Constitution (I. Spec-First)**: Este spec MUST ser agnóstico de stack.
> No mencionar Laravel, React, Inertia.js ni rutas de código. El CÓMO va en `plan.md`.

---

## Resumen del problema

El sistema confía, en varios puntos críticos, en que los datos relacionados (referencias entre entidades) y los datos introducidos por el usuario tienen siempre la forma esperada. La evidencia recogida contra el sistema real demuestra que no es así: entre el 40% y el 84% de ciertas referencias de negocio están vacías con frecuencia, y varios formularios permiten escribir más texto del que el campo de destino puede almacenar. Cuando esto ocurre hoy, el sistema se interrumpe con un error no controlado (pantalla de error, mensaje técnico interno) en vez de guiar al usuario o degradar el flujo de forma controlada.

Esta feature blinda los puntos de mayor impacto de negocio (generación de anexos y documentos, tramitación de contratos, alta/edición de las entidades principales del dominio) frente a estos dos tipos de fallo, y añade un mecanismo para evitar que la brecha vuelva a crecer sin ser detectada.

---

## User Scenarios & Testing *(mandatory)*

### User Story 1 — La generación de anexos y documentos no se interrumpe por datos de referencia incompletos (Priority: P1)

Como usuario que genera un anexo comercial (cambio de potencia, cambio de titular, anexo de producto) o tramita la integración de un contrato con una comercializadora externa, necesito que el sistema me informe con un mensaje claro cuando la propuesta o el dato de referencia no existe o está incompleto, en vez de mostrarme una pantalla de error técnico.

**Why this priority**: Es el fallo más grave identificado: se reproduce el 100% de las veces que se dan las condiciones (id inválido, propuesta sin cliente asociado), no depende de la distribución de los datos, e interrumpe el flujo de negocio de mayor valor (cierre de contratos).

**Independent Test**: Solicitar la generación de un anexo con un identificador de propuesta que no existe (o ya fue eliminada) y verificar que el sistema responde con un mensaje de error de negocio, no con una interrupción no controlada. Repetir con una propuesta real que no tenga ningún cliente asociado.

**Acceptance Scenarios**:

1. **Given** un identificador de propuesta comercial que no existe, **When** el usuario solicita la generación de cualquier anexo (potencia, titular, producto, contrato dual) para esa propuesta, **Then** el sistema responde con un mensaje de error de negocio comprensible y no interrumpe el proceso completo del servidor.
2. **Given** una propuesta comercial real sin ningún cliente asociado, **When** se ejecuta la tramitación/integración de esa propuesta, **Then** el sistema detiene el proceso con un mensaje claro sobre el dato faltante, sin afectar a otras tramitaciones en curso.
3. **Given** una línea de propuesta sin comercializadora, producto o anexo comercial asignado (caso frecuente, no excepcional), **When** se genera el PDF o se consulta su detalle, **Then** el documento se genera mostrando el dato como "no asignado" en vez de fallar.

---

### User Story 2 — La integración con comercializadoras externas no falla en cascada ni expone detalles internos (Priority: P1)

Como usuario que da de alta o edita Clientes, CUPS, Comercializadoras, Productos, Anexos de Producto o Puntos de Suministro, necesito que si la respuesta de un paso interno de guardado no tiene el formato esperado, el sistema me muestre un mensaje de error de negocio y registre el detalle técnico solo en el log, sin mostrarme el mensaje interno de la excepción.

**Why this priority**: El mismo patrón de riesgo se repite en al menos 8 puntos de guardado distintos del sistema; además, algunos de esos puntos hoy filtran el mensaje interno de la base de datos al usuario, lo cual es también un riesgo de exposición de información.

**Independent Test**: Forzar una respuesta interna inesperada (o nula) en un flujo de guardado y verificar que el usuario recibe un mensaje genérico de error, mientras el detalle técnico completo queda registrado en el log del sistema.

**Acceptance Scenarios**:

1. **Given** que un paso interno de guardado no devuelve el resultado esperado, **When** el sistema intenta leer el resultado de esa operación, **Then** responde con un error de negocio controlado en vez de una interrupción no controlada.
2. **Given** que ocurre un error inesperado durante el guardado de un CUPS, un cliente o cualquier entidad del dominio, **When** el sistema construye la respuesta de error para el usuario, **Then** el mensaje mostrado es genérico y no contiene el texto interno de la excepción ni detalles de la estructura de la base de datos; el detalle completo se registra en el log.
3. **Given** que una carga masiva de puntos de suministro encuentra una fila con un dato de comercializadora no resuelto, **When** el proceso continúa con las siguientes filas, **Then** esa fila concreta se reporta como rechazada al final del proceso, sin usar un dato a medio construir como si fuera válido.

---

### User Story 3 — El alta y edición de Clientes y Contactos rechaza de forma controlada el texto que excede el límite permitido (Priority: P1)

Como usuario que da de alta o edita un cliente o un contacto, necesito que el sistema me avise en el momento en que escribo un dato demasiado largo (razón social, dirección, observaciones, teléfono, IBAN), en vez de dejarme completar y enviar el formulario para luego recibir un error del servidor.

**Why this priority**: Es el módulo de mayor uso diario del sistema; los mismatches de longitud detectados llegan a ser de hasta 5 veces el límite real permitido por el dato de destino, y el campo de observaciones de cliente no tiene ningún límite en ningún nivel.

**Independent Test**: Introducir en el campo de observaciones de un cliente un texto de más de 200 caracteres y verificar que el sistema lo rechaza antes de enviarlo, mostrando cuántos caracteres sobran.

**Acceptance Scenarios**:

1. **Given** un campo de texto de un formulario de cliente o contacto con un límite de longitud conocido, **When** el usuario escribe más caracteres de los permitidos, **Then** el sistema impide seguir escribiendo o marca visualmente el exceso antes de enviar el formulario.
2. **Given** que un dato llega al sistema desde un punto distinto al formulario estándar (por ejemplo, una integración) y excede el límite permitido, **When** el sistema intenta guardarlo, **Then** rechaza la operación con un mensaje de validación de negocio, no con un error de base de datos.
3. **Given** un IBAN de contacto con formato internacional válido pero más largo que el límite interno actual, **When** el usuario lo introduce, **Then** el sistema lo valida con la misma regla de formato y longitud ya usada para el IBAN de cuentas bancarias de cobro.

---

### User Story 4 — El alta y edición de Puntos de Suministro y CUPS exige y valida los datos mínimos antes de guardar (Priority: P1)

Como usuario que da de alta o edita un CUPS (eléctrico o de gas) de forma independiente, necesito que el sistema exija los datos obligatorios y valide su formato antes de guardar, ya que hoy este es el único punto de escritura del sistema que no aplica ninguna regla, ni siquiera de obligatoriedad.

**Why this priority**: Es el hallazgo más crítico del análisis de validaciones: la ausencia total de reglas permite guardar un CUPS vacío, duplicado, mal formado o asociado a un punto de suministro inexistente, y solo lo detiene —parcialmente— la base de datos.

**Independent Test**: Intentar guardar un CUPS eléctrico sin código y verificar que el sistema lo rechaza con un mensaje de validación antes de intentar guardarlo. Intentar guardar un CUPS asociado a un punto de suministro inexistente y verificar el mismo comportamiento.

**Acceptance Scenarios**:

1. **Given** un formulario de alta de CUPS eléctrico o de gas, **When** el usuario intenta guardar sin código de CUPS, **Then** el sistema exige el dato antes de enviar la solicitud.
2. **Given** un código de CUPS con un formato que no corresponde al estándar (prefijo de país, longitud), **When** el usuario intenta guardarlo, **Then** el sistema lo rechaza indicando el formato esperado.
3. **Given** un punto de suministro que no existe, **When** se intenta asociar un CUPS a ese punto de suministro, **Then** el sistema rechaza la operación en vez de crear una referencia huérfana.
4. **Given** un punto de suministro con campos de dirección (bloque, escalera, planta, puerta) que exceden su límite real de almacenamiento, **When** el usuario los completa, **Then** el sistema avisa del límite antes de enviar el formulario.

---

### User Story 5 — El alta y edición de Contratos valida formato y longitud de los datos de tarifa, CUPS y fechas (Priority: P2)

Como usuario que da de alta o edita un contrato (unipunto, multipunto o multicliente-multipunto), necesito que los datos de tarifa, comentarios y fechas se validen de forma consistente en los tres flujos, para no encontrarme con un guardado fallido a mitad de un alta compleja.

**Why this priority**: Los tres flujos de alta de contrato comparten el mismo vacío de validación (ningún campo usa validación de formato en el navegador), y la falta de coherencia entre flujos (por ejemplo, la validación de fecha fin solo existe en uno de los tres) genera comportamiento inconsistente para el mismo tipo de operación.

**Independent Test**: Completar un alta de contrato multipunto con una fecha de fin anterior a la fecha de inicio y verificar que el sistema la rechaza, igual que ya ocurre en el flujo unipunto.

**Acceptance Scenarios**:

1. **Given** cualquiera de los tres flujos de alta de contrato, **When** el usuario introduce una fecha de fin anterior a la fecha de inicio, **Then** el sistema la rechaza de forma consistente en los tres flujos.
2. **Given** un campo de identificador fiscal o razón social editado manualmente por el usuario (no tomado directamente de un resultado de búsqueda), **When** excede la longitud permitida, **Then** el sistema lo señala antes de enviar el formulario.
3. **Given** un dato de tarifa (identificador de tarifa, nombre, código de producto, versión) recibido de un catálogo externo, **When** se guarda en la línea de contrato, **Then** el sistema aplica el mismo límite de longitud tanto en el alta como en la edición posterior.

---

### User Story 6 — Comercializadoras, Productos, Anexos de Producto y la carga masiva de puntos de suministro rechazan datos fuera de rango de forma controlada (Priority: P2)

Como usuario administrador que mantiene el catálogo de comercializadoras, productos y anexos comerciales, o que realiza una carga masiva de puntos de suministro desde una hoja de cálculo, necesito que el sistema valide la longitud real de cada dato antes de guardarlo, y que una carga masiva me indique qué filas concretas fueron rechazadas y por qué, en vez de interrumpir todo el proceso sin explicación.

**Why this priority**: El catálogo de comercializadoras tiene el mismatch de longitud más extremo detectado (hasta 25 veces el límite real); la carga masiva, al procesar muchas filas a la vez, es donde el volumen de impacto de un solo dato mal formado es mayor.

**Independent Test**: Cargar un archivo de puntos de suministro con una fila cuyo campo de planta o puerta exceda el límite real de almacenamiento, y verificar que el proceso continúa con el resto de filas y reporta esa fila como rechazada con el motivo.

**Acceptance Scenarios**:

1. **Given** un formulario de alta o edición de comercializadora, producto o anexo de producto, **When** un campo de texto excede su límite real, **Then** el sistema lo rechaza antes de enviarlo, con el mismo límite reflejado en el navegador y en el servidor.
2. **Given** un archivo de carga masiva de puntos de suministro con una o más filas que exceden algún límite de longitud, **When** se procesa el archivo, **Then** las filas válidas se guardan con normalidad y las filas inválidas se reportan de forma individual al finalizar, sin detener el resto del lote.

---

### User Story 7 — Las relaciones de datos del dominio reflejan correctamente su vínculo real (Priority: P2)

Como usuario que consulta el contacto de un cliente, la localidad de un punto de suministro o la comercializadora de una propuesta comercial, necesito que el sistema me muestre el dato correcto, no uno vacío o incorrecto por una relación mal definida internamente.

**Why this priority**: No genera una interrupción visible del sistema, pero produce datos incorrectos o vacíos de forma silenciosa — un tipo de fallo más difícil de detectar y que erosiona la confianza en los datos mostrados.

**Independent Test**: Consultar el contacto asociado a un cliente que sí tiene un contacto registrado y verificar que el dato mostrado corresponde al contacto real, no a uno vacío o distinto.

**Acceptance Scenarios**:

1. **Given** un cliente con un contacto registrado, **When** se consulta el contacto asociado desde la ficha del cliente, **Then** el sistema muestra el contacto correcto.
2. **Given** un punto de suministro, **When** se consulta su provincia, **Then** el sistema muestra el dato de localidad correcto bajo un nombre que refleja lo que realmente representa.

---

### User Story 8 — El equipo técnico puede detectar automáticamente nuevas discrepancias de longitud antes de que lleguen a producción (Priority: P3)

Como responsable técnico del sistema, necesito una forma de verificar, de un vistazo, que los límites de validación de cualquier formulario siguen alineados con el límite real del dato de destino, para no depender de una auditoría manual completa cada vez que se sospeche un problema similar.

**Why this priority**: Es una inversión preventiva, no corrige un fallo existente, pero evita que la brecha detectada por este análisis vuelva a crecer sin ser detectada a medida que se crean nuevos formularios.

**Independent Test**: Ejecutar la herramienta de verificación y comprobar que reporta correctamente al menos un caso conocido de discrepancia (por ejemplo, uno de los ya identificados y no corregido en esta iteración, si lo hay) o que reporta "sin discrepancias" tras aplicar todas las correcciones de esta feature.

**Acceptance Scenarios**:

1. **Given** un límite de validación de un formulario que no coincide con el límite real del dato de destino, **When** se ejecuta la herramienta de verificación, **Then** reporta esa discrepancia identificando el campo y ambos límites.
2. **Given** que todos los límites están alineados, **When** se ejecuta la herramienta, **Then** reporta que no hay discrepancias.

---

### Edge Cases

- ¿Qué ocurre si el propio proceso de registrar el error en el log falla (por ejemplo, disco lleno)? El usuario debe seguir recibiendo el mensaje genérico; el fallo de log no debe convertirse en un segundo error visible.
- ¿Cómo se comporta el sistema si dos usuarios editan la misma entidad al mismo tiempo y uno de ellos introduce un dato que ahora se rechaza por longitud, pero el otro ya lo había guardado antes de esta corrección? El dato ya guardado no se modifica retroactivamente; solo se valida en el siguiente guardado.
- ¿Qué pasa si una integración externa (comercializadora) empieza a enviar un campo de catálogo (nombre de tarifa, código de producto) más largo que el límite actual? Debe rechazarse con un registro claro del motivo, no con una interrupción no controlada, para poder diagnosticar el cambio en el origen externo.
- ¿Qué ocurre con los datos que ya existen en el sistema y que hoy exceden los nuevos límites que se van a aplicar (por ejemplo, si ya hay un cliente con una observación de 300 caracteres en un campo que se limitará a 200)? Esta feature valida escritura nueva; no trunca ni modifica datos históricos ya almacenados.
- ¿Qué pasa si la carga masiva completa no tiene ninguna fila válida? El sistema debe reportarlo como un fallo total del archivo, no como "0 filas rechazadas".

---

## Requirements *(mandatory)*

### Functional Requirements

- **FR-001**: El sistema MUST verificar la existencia de cualquier referencia de negocio (identificador de propuesta, cliente, punto de suministro) antes de usar cualquiera de sus datos relacionados, y MUST hacerlo antes de la primera operación que dependa de esa referencia, no después.
- **FR-002**: Cuando una referencia de negocio no existe o está incompleta, el sistema MUST responder con un mensaje de error de negocio comprensible para el usuario, sin interrumpir el proceso del servidor de forma no controlada.
- **FR-003**: El sistema MUST tolerar que un dato relacionado opcional (comercializadora, producto, anexo comercial asignado a una línea de propuesta) esté ausente, mostrando un valor por defecto explícito ("no asignado") en vez de fallar.
- **FR-004**: El sistema MUST comprobar que el resultado de cualquier paso interno de guardado tiene el formato esperado antes de leer sus datos, en todos los flujos de alta/edición de Clientes, Contactos, CUPS, Comercializadoras, Productos, Anexos de Producto y Puntos de Suministro.
- **FR-005**: Cuando ocurre un error inesperado durante el guardado de cualquier entidad del dominio, el sistema MUST mostrar al usuario un mensaje genérico y MUST registrar el detalle técnico completo únicamente en el log del sistema.
- **FR-006**: El sistema MUST exigir y validar en el formulario, antes del envío, el código de CUPS eléctrico y de gas (obligatoriedad, formato de prefijo de país y longitud).
- **FR-007**: El sistema MUST rechazar la asociación de un CUPS a un punto de suministro que no existe.
- **FR-008**: El sistema MUST alinear el límite de longitud aplicado en el navegador, en el servidor y en el almacenamiento final para todo campo de texto de los formularios de Clientes, Contactos, Cuentas Bancarias, Puntos de Suministro, CUPS, Contratos, Comercializadoras, Productos y Anexos de Producto — el límite más estricto de los tres MUST aplicarse ya en el navegador.
- **FR-009**: El sistema MUST validar el IBAN de contacto personal con la misma regla de formato y longitud ya aplicada al IBAN de cuenta bancaria de cobro.
- **FR-010**: El sistema MUST validar de forma consistente que la fecha de fin de un suministro no sea anterior a su fecha de inicio, en los tres flujos de alta de contrato (unipunto, multipunto, multicliente-multipunto).
- **FR-011**: El sistema MUST aplicar el mismo límite de longitud a los datos de tarifa de una línea de contrato (identificador, nombre, código de producto, versión) tanto en el alta como en la edición posterior.
- **FR-012**: Durante una carga masiva de puntos de suministro, el sistema MUST validar la longitud y el tipo de cada dato por fila antes de intentar guardarla, y MUST continuar procesando el resto del archivo cuando una fila individual es rechazada, reportando al final la lista de filas rechazadas con su motivo.
- **FR-013**: El sistema MUST corregir la relación entre un cliente y su contacto para que use el identificador correcto de vínculo.
- **FR-014**: El sistema MUST exponer correctamente el dato de localidad de un punto de suministro bajo una denominación que refleje lo que realmente representa.
- **FR-015**: El sistema SHOULD proveer una forma de verificar automáticamente que los límites de longitud de cualquier formulario siguen alineados con el límite real de su dato de destino, para detectar discrepancias futuras sin auditoría manual completa.

### Key Entities

- **Propuesta Comercial**: Solicitud de contratación en curso; puede tener o no un cliente, una comercializadora, un producto y un anexo comercial asociados. Es la entidad central de la que dependen los anexos y la tramitación.
- **Cliente**: Persona o entidad titular de uno o más puntos de suministro; puede tener uno o más contactos asociados.
- **Contacto**: Persona de contacto vinculada a un cliente, con sus propios datos de comunicación (teléfono, IBAN personal).
- **Punto de Suministro**: Ubicación física de consumo, con dirección estructurada (vía, bloque, escalera, planta, puerta) y una o más referencias CUPS (eléctrico y/o gas).
- **CUPS**: Código único de suministro (eléctrico o de gas); puede existir de forma independiente o asociado a un punto de suministro.
- **Comercializadora**: Entidad que provee el servicio energético; tiene un catálogo de productos y anexos comerciales propios.
- **Contrato**: Resultado de una propuesta comercial aceptada; agrupa las líneas de suministro con sus datos de tarifa.
- **Carga Masiva**: Proceso de alta de múltiples puntos de suministro a partir de un archivo, fila a fila.

---

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: Generar un anexo o tramitar una integración con un identificador de propuesta inexistente produce un mensaje de error de negocio en el 100% de los casos, nunca una interrupción no controlada del servidor.
- **SC-002**: El guardado de un CUPS sin código, con formato inválido o asociado a un punto de suministro inexistente es rechazado antes de intentar guardarlo en el 100% de los casos.
- **SC-003**: Ningún mensaje de error mostrado al usuario en los flujos de guardado cubiertos por esta feature contiene el texto interno de una excepción técnica o del motor de base de datos.
- **SC-004**: El límite de caracteres aplicado en el navegador coincide con el límite real de almacenamiento en el 100% de los campos de texto de los formularios cubiertos por esta feature.
- **SC-005**: Una carga masiva con filas inválidas mezcladas con filas válidas completa el procesamiento de todas las filas válidas y reporta individualmente cada fila rechazada, en vez de abortar el archivo completo.
- **SC-006**: La herramienta de verificación de límites de longitud, ejecutada tras completar esta feature, reporta cero discrepancias en los formularios cubiertos.

## Assumptions

- El alcance de esta feature son los flujos de mayor impacto de negocio identificados en el análisis (generación de anexos, integración de contratos, y las entidades principales del dominio: Clientes, Contactos, Cuentas Bancarias, Puntos de Suministro, CUPS, Contratos, Comercializadoras, Productos, Anexos de Producto, Carga Masiva). No incluye módulos de solo lectura (consultas SIPS, filtros de búsqueda, Caes) ni el módulo de Usuarios/Perfil, salvo mención puntual de bajo esfuerzo.
- Los datos ya almacenados que hoy exceden los nuevos límites de longitud no se modifican como parte de esta feature; solo se valida la escritura nueva.
- El límite de longitud real de cada dato de destino se toma como válido tal como fue documentado en el análisis previo (obtenido por consulta directa contra el sistema); no se vuelve a auditar la base de datos completa como parte de esta feature, salvo en los puntos donde se detecte una discrepancia durante la implementación.
- La corrección de las relaciones de datos del dominio (User Story 7) se implementa con especial cuidado de no alterar el comportamiento de ninguna consulta existente que dependa hoy del comportamiento actual, aunque sea incorrecto — cualquier caso donde esto no pueda garantizarse se documenta como riesgo abierto antes de implementarse.
- No se requiere automatizar pruebas nuevas como parte del alcance obligatorio de esta feature; la verificación es manual dirigida a los escenarios de aceptación de cada historia de usuario.
