소스 검색

better ux for autorization errors

Matthew Trejo 1 개월 전
부모
커밋
6f69fbf2cf

BIN
prisma/dev.db


+ 24 - 9
src/app/api/poll-sri/route.ts

@@ -1,5 +1,6 @@
 import { NextRequest, NextResponse } from 'next/server'
 import { longPollDoc } from '@/lib/sri-utils'
+import { formatSRIError, isCriticalError, isRecoverableError } from '@/lib/sri-error-codes'
 
 export async function POST(request: NextRequest) {
   try {
@@ -23,8 +24,11 @@ export async function POST(request: NextRequest) {
       ambiente: ambienteValido
     })
 
-    // Obtener la autorización del resultado
-    const autorizacion = result.RespuestaAutorizacionComprobante.autorizaciones?.autorizacion?.[0]
+    // Obtener la autorización del resultado (puede ser objeto o array)
+    let autorizacion = result.RespuestaAutorizacionComprobante.autorizaciones?.autorizacion
+    if (Array.isArray(autorizacion)) {
+      autorizacion = autorizacion[0]
+    }
 
     // Determinar el estado basado en la respuesta
     let estado: string = 'verificando'
@@ -39,13 +43,24 @@ export async function POST(request: NextRequest) {
       }
     }
 
-    // Formatear mensajes
-    const mensajes = autorizacion?.mensajes?.mensaje?.map(m => ({
-      identificador: m.identificador,
-      mensaje: m.mensaje,
-      tipo: m.tipo as "INFORMATIVO" | "ADVERTENCIA" | "ERROR",
-      informacionAdicional: m.informacionAdicional
-    })) || []
+    // Formatear mensajes con información adicional
+    let mensajes: any[] = []
+    if (autorizacion?.mensajes?.mensaje) {
+      const mensajesRaw = Array.isArray(autorizacion.mensajes.mensaje)
+        ? autorizacion.mensajes.mensaje
+        : [autorizacion.mensajes.mensaje]
+
+      mensajes = mensajesRaw.map((m: any) => ({
+        identificador: m.identificador,
+        mensaje: m.mensaje,
+        tipo: m.tipo as "INFORMATIVO" | "ADVERTENCIA" | "ERROR",
+        informacionAdicional: m.informacionAdicional,
+        // Agregar información formateada para mostrar al usuario
+        mensajeFormateado: formatSRIError(m.identificador, m.mensaje, m.informacionAdicional),
+        esCritico: isCriticalError(m.identificador),
+        esRecuperable: isRecoverableError(m.identificador)
+      }))
+    }
 
     return NextResponse.json({
       success: true,

+ 53 - 6
src/components/app-sidebar.tsx

@@ -24,28 +24,36 @@ import {
 } from "@/components/ui/sidebar"
 import { ModeToggle } from "@/components/mode-toggle"
 
-// Menu items
-const items = [
+// Menu items - Navegación principal
+const navigationItems = [
   {
     title: "Inicio",
     url: "/",
     icon: Home,
   },
+]
+
+// Menu items - Facturación Electrónica
+const invoiceItems = [
   {
-    title: "Crear Factura (XML)",
+    title: "Crear Factura",
     url: "/factura",
     icon: FileText,
   },
   {
-    title: "Firmar Factura (XML)",
+    title: "Firmar Factura",
     url: "/firmar",
     icon: FileSignature,
   },
   {
-    title: "Enviar al SRI (SOAP)",
+    title: "Enviar al SRI",
     url: "/enviar-sri",
     icon: Send,
   },
+]
+
+// Menu items - Configuración
+const settingsItems = [
   {
     title: "Configuración",
     url: "/configuracion",
@@ -72,11 +80,50 @@ export function AppSidebar() {
         </SidebarMenu>
       </SidebarHeader>
       <SidebarContent>
+        {/* Navegación Principal */}
         <SidebarGroup>
           <SidebarGroupLabel>Navegación</SidebarGroupLabel>
           <SidebarGroupContent>
             <SidebarMenu>
-              {items.map((item) => (
+              {navigationItems.map((item) => (
+                <SidebarMenuItem key={item.title}>
+                  <SidebarMenuButton asChild>
+                    <a href={item.url}>
+                      <item.icon />
+                      <span>{item.title}</span>
+                    </a>
+                  </SidebarMenuButton>
+                </SidebarMenuItem>
+              ))}
+            </SidebarMenu>
+          </SidebarGroupContent>
+        </SidebarGroup>
+
+        {/* Facturación Electrónica */}
+        <SidebarGroup>
+          <SidebarGroupLabel>Facturación Electrónica</SidebarGroupLabel>
+          <SidebarGroupContent>
+            <SidebarMenu>
+              {invoiceItems.map((item) => (
+                <SidebarMenuItem key={item.title}>
+                  <SidebarMenuButton asChild>
+                    <a href={item.url}>
+                      <item.icon />
+                      <span>{item.title}</span>
+                    </a>
+                  </SidebarMenuButton>
+                </SidebarMenuItem>
+              ))}
+            </SidebarMenu>
+          </SidebarGroupContent>
+        </SidebarGroup>
+
+        {/* Configuración */}
+        <SidebarGroup>
+          <SidebarGroupLabel>Sistema</SidebarGroupLabel>
+          <SidebarGroupContent>
+            <SidebarMenu>
+              {settingsItems.map((item) => (
                 <SidebarMenuItem key={item.title}>
                   <SidebarMenuButton asChild>
                     <a href={item.url}>

+ 1 - 1
src/components/envio-sri/EnviarHeader.tsx

@@ -10,7 +10,7 @@ export function EnviarHeader() {
         <div>
           <h1 className="text-3xl font-bold">Enviar al SRI</h1>
           <p className="text-muted-foreground">
-            Envía tus documentos electrónicos al Servicio de Rentas Internas
+            Envía tus documentos electrónicos al SRI
           </p>
         </div>
       </div>

+ 35 - 11
src/components/envio-sri/EnvioStatus.tsx

@@ -99,7 +99,7 @@ export function EnvioStatus({ estadoEnvio, respuestaAutorizacion }: EnvioStatusP
 
             {respuestaAutorizacion.fechaAutorizacion && (
               <div className="space-y-1">
-                <p className="text-sm font-medium">Fecha de Autorización</p>
+                <p className="text-sm font-medium">Fecha o Intento de Autorización</p>
                 <p className="text-sm text-muted-foreground">
                   {respuestaAutorizacion.fechaAutorizacion}
                 </p>
@@ -119,13 +119,13 @@ export function EnvioStatus({ estadoEnvio, respuestaAutorizacion }: EnvioStatusP
             {respuestaAutorizacion.mensajes && respuestaAutorizacion.mensajes.length > 0 && (
               <div className="space-y-2">
                 <p className="text-sm font-medium">Mensajes del SRI</p>
-                <div className="space-y-2">
+                <div className="space-y-3">
                   {respuestaAutorizacion.mensajes.map((mensaje, index) => (
                     <Alert
                       key={index}
                       variant={mensaje.tipo === "ERROR" ? "destructive" : "default"}
                     >
-                      <AlertDescription className="space-y-1">
+                      <AlertDescription className="space-y-2">
                         <div className="flex items-start gap-2">
                           <Badge
                             variant={
@@ -139,14 +139,38 @@ export function EnvioStatus({ estadoEnvio, respuestaAutorizacion }: EnvioStatusP
                           >
                             {mensaje.tipo}
                           </Badge>
-                          <div className="flex-1 space-y-1">
-                            <p className="text-sm font-medium">
-                              {mensaje.identificador}: {mensaje.mensaje}
-                            </p>
-                            {mensaje.informacionAdicional && (
-                              <p className="text-xs text-muted-foreground">
-                                {mensaje.informacionAdicional}
-                              </p>
+                          <div className="flex-1 space-y-2">
+                            {/* Mostrar mensaje formateado si está disponible */}
+                            {mensaje.mensajeFormateado ? (
+                              <div className="space-y-2">
+                                <pre className="text-sm whitespace-pre-wrap font-sans">
+                                  {mensaje.mensajeFormateado}
+                                </pre>
+
+                                {/* Indicador de tipo de error */}
+                                {mensaje.esCritico && (
+                                  <Badge variant="destructive" className="text-xs">
+                                    ⚠️ Error Crítico - Requiere intervención administrativa
+                                  </Badge>
+                                )}
+                                {mensaje.esRecuperable && (
+                                  <Badge variant="secondary" className="text-xs">
+                                    🔧 Error Recuperable - Puedes corregir el documento
+                                  </Badge>
+                                )}
+                              </div>
+                            ) : (
+                              /* Fallback al formato anterior */
+                              <>
+                                <p className="text-sm font-medium">
+                                  {mensaje.identificador}: {mensaje.mensaje}
+                                </p>
+                                {mensaje.informacionAdicional && (
+                                  <p className="text-xs text-muted-foreground">
+                                    {mensaje.informacionAdicional}
+                                  </p>
+                                )}
+                              </>
                             )}
                           </div>
                         </div>

+ 7 - 4
src/hooks/factura/useXmlGeneration.ts

@@ -1,5 +1,5 @@
 import { toast } from "sonner"
-import { validateRUC } from "@/utils/factura/validations"
+import { validateRUC, formatDateForSRI } from "@/utils/factura/validations"
 import { generateClaveAcceso } from "@/utils/factura/claveAcceso"
 import type { InfoTributaria, InfoFactura, DetalleItem, TotalImpuesto } from "@/types/factura"
 
@@ -56,7 +56,7 @@ export function useXmlGeneration() {
   </infoTributaria>
   
   <infoFactura>
-    <fechaEmision>${infoFactura.fechaEmision}</fechaEmision>
+    <fechaEmision>${formatDateForSRI(infoFactura.fechaEmision)}</fechaEmision>
     <dirEstablecimiento>${infoFactura.dirEstablecimiento}</dirEstablecimiento>
     <obligadoContabilidad>${infoFactura.obligadoContabilidad}</obligadoContabilidad>
     <tipoIdentificacionComprador>${infoFactura.tipoIdentificacionComprador}</tipoIdentificacionComprador>
@@ -99,16 +99,19 @@ export function useXmlGeneration() {
   <detalles>`
 
     detalles.forEach(item => {
+      // Si codigoAuxiliar está vacío, usar el codigoPrincipal como fallback
+      const codigoAux = item.codigoAuxiliar?.trim() || item.codigoPrincipal || '001'
+
       xml += `
     <detalle>
       <codigoPrincipal>${item.codigoPrincipal}</codigoPrincipal>
-      <codigoAuxiliar>${item.codigoAuxiliar}</codigoAuxiliar>
+      <codigoAuxiliar>${codigoAux}</codigoAuxiliar>
       <descripcion>${item.descripcion}</descripcion>
       <cantidad>${item.cantidad}</cantidad>
       <precioUnitario>${parseFloat(item.precioUnitario).toFixed(6)}</precioUnitario>
       <descuento>${item.descuento}</descuento>
       <precioTotalSinImpuesto>${item.precioTotalSinImpuesto}</precioTotalSinImpuesto>
-      
+
       <impuestos>
         <impuesto>
           <codigo>2</codigo>

+ 8 - 4
src/lib/factura/xml-generator.ts

@@ -1,4 +1,5 @@
 import type { InfoTributaria, InfoFactura, DetalleItem, TotalImpuesto } from "@/types/factura"
+import { formatDateForSRI } from "@/utils/factura/validations"
 
 export class XmlGenerator {
   static generateFacturaXml(
@@ -22,9 +23,9 @@ export class XmlGenerator {
     <secuencial>${infoTributaria.secuencial}</secuencial>
     <dirMatriz>${infoTributaria.dirMatriz}</dirMatriz>
   </infoTributaria>
-  
+
   <infoFactura>
-    <fechaEmision>${infoFactura.fechaEmision}</fechaEmision>
+    <fechaEmision>${formatDateForSRI(infoFactura.fechaEmision)}</fechaEmision>
     <dirEstablecimiento>${infoFactura.dirEstablecimiento}</dirEstablecimiento>
     <obligadoContabilidad>${infoFactura.obligadoContabilidad}</obligadoContabilidad>
     <tipoIdentificacionComprador>${infoFactura.tipoIdentificacionComprador}</tipoIdentificacionComprador>
@@ -65,16 +66,19 @@ export class XmlGenerator {
   <detalles>`
 
     detalles.forEach(item => {
+      // Si codigoAuxiliar está vacío, usar el codigoPrincipal como fallback
+      const codigoAux = item.codigoAuxiliar?.trim() || item.codigoPrincipal || '001'
+
       xml += `
     <detalle>
       <codigoPrincipal>${item.codigoPrincipal}</codigoPrincipal>
-      <codigoAuxiliar>${item.codigoAuxiliar}</codigoAuxiliar>
+      <codigoAuxiliar>${codigoAux}</codigoAuxiliar>
       <descripcion>${item.descripcion}</descripcion>
       <cantidad>${item.cantidad}</cantidad>
       <precioUnitario>${parseFloat(item.precioUnitario).toFixed(6)}</precioUnitario>
       <descuento>${item.descuento}</descuento>
       <precioTotalSinImpuesto>${item.precioTotalSinImpuesto}</precioTotalSinImpuesto>
-      
+
       <impuestos>
         <impuesto>
           <codigo>2</codigo>

+ 268 - 0
src/lib/sri-error-codes.ts

@@ -0,0 +1,268 @@
+/**
+ * Códigos de error del SRI con descripciones y soluciones sugeridas
+ * Basado en la documentación oficial del SRI
+ */
+
+interface ErrorSRI {
+  codigo: string
+  descripcion: string
+  motivo: string
+  solucion?: string
+  validacion: 'RECEPCIÓN' | 'AUTORIZACIÓN' | 'EMISOR' | 'EMISOR/RECEPCIÓN'
+}
+
+export const ERRORES_SRI: Record<string, ErrorSRI> = {
+  '2': {
+    codigo: '2',
+    descripcion: 'RUC del emisor se encuentra NO ACTIVO',
+    motivo: 'El número de RUC no está activo en el registro del SRI',
+    solucion: 'Verifica que tu RUC esté en estado ACTIVO en el portal del SRI',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '10': {
+    codigo: '10',
+    descripcion: 'Establecimiento del emisor se encuentra Clausurado',
+    motivo: 'El establecimiento emisor ha sido clausurado',
+    solucion: 'Espera a que concluya la clausura del establecimiento. El servicio se habilitará automáticamente',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '26': {
+    codigo: '26',
+    descripcion: 'Tamaño máximo superado',
+    motivo: 'El tamaño del archivo supera lo establecido',
+    solucion: 'Reduce el tamaño del archivo o la cantidad de detalles en la factura',
+    validacion: 'RECEPCIÓN'
+  },
+  '27': {
+    codigo: '27',
+    descripcion: 'Clase no permitida',
+    motivo: 'La clase del contribuyente no puede emitir comprobantes electrónicos',
+    solucion: 'Verifica tu tipo de contribuyente en el SRI',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '28': {
+    codigo: '28',
+    descripcion: 'Acuerdo de medios electrónicos no aceptado',
+    motivo: 'No has aceptado el acuerdo de medios electrónicos del SRI',
+    solucion: 'Ingresa al portal del SRI y acepta el acuerdo de medios electrónicos',
+    validacion: 'RECEPCIÓN'
+  },
+  '35': {
+    codigo: '35',
+    descripcion: 'Documento inválido',
+    motivo: 'El XML no cumple con la estructura del esquema XSD del SRI',
+    solucion: 'Verifica que todos los campos requeridos estén completos y con el formato correcto',
+    validacion: 'RECEPCIÓN'
+  },
+  '36': {
+    codigo: '36',
+    descripcion: 'Versión esquema descontinuada',
+    motivo: 'La versión del esquema XML no es la correcta',
+    solucion: 'Actualiza la versión del esquema XML en tu factura (usar versión 1.1.0 o superior)',
+    validacion: 'RECEPCIÓN'
+  },
+  '37': {
+    codigo: '37',
+    descripcion: 'RUC sin autorización de emisión',
+    motivo: 'El RUC no cuenta con autorización para emitir comprobantes electrónicos',
+    solucion: 'Solicita la autorización de emisión de comprobantes electrónicos en el portal del SRI',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '39': {
+    codigo: '39',
+    descripcion: 'Firma inválida',
+    motivo: 'La firma electrónica no es válida o el certificado no es de tipo firma digital',
+    solucion: 'Verifica que estés usando un certificado de firma digital válido (no de SSL/TLS). Debe ser emitido por una entidad certificadora autorizada en Ecuador',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '40': {
+    codigo: '40',
+    descripcion: 'Error en el certificado',
+    motivo: 'No se encontró el certificado o no se puede convertir en certificado X509',
+    solucion: 'Verifica que el archivo del certificado (.p12) sea válido y esté en el formato correcto',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '43': {
+    codigo: '43',
+    descripcion: 'Clave de acceso registrada',
+    motivo: 'La clave de acceso ya existe en el sistema del SRI',
+    solucion: 'Genera una nueva factura con un secuencial diferente. No reenvíes la misma factura',
+    validacion: 'RECEPCIÓN'
+  },
+  '45': {
+    codigo: '45',
+    descripcion: 'Secuencial registrado',
+    motivo: 'El secuencial del comprobante ya fue usado',
+    solucion: 'Incrementa el secuencial para generar una nueva factura',
+    validacion: 'RECEPCIÓN'
+  },
+  '46': {
+    codigo: '46',
+    descripcion: 'RUC no existe',
+    motivo: 'El RUC del emisor no existe en el Registro Único de Contribuyentes',
+    solucion: 'Verifica que el RUC esté escrito correctamente (13 dígitos)',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '47': {
+    codigo: '47',
+    descripcion: 'Tipo de comprobante no existe',
+    motivo: 'El código de tipo de comprobante no es válido',
+    solucion: 'Verifica que el código de documento (codDoc) sea correcto (01 para facturas)',
+    validacion: 'RECEPCIÓN'
+  },
+  '48': {
+    codigo: '48',
+    descripcion: 'Esquema XSD no existe',
+    motivo: 'No existe esquema para el tipo de comprobante enviado',
+    solucion: 'Verifica el tipo de comprobante y la versión del esquema',
+    validacion: 'RECEPCIÓN'
+  },
+  '49': {
+    codigo: '49',
+    descripcion: 'Argumentos nulos',
+    motivo: 'Se enviaron argumentos nulos al servicio web',
+    solucion: 'Verifica que todos los campos requeridos tengan valores',
+    validacion: 'RECEPCIÓN'
+  },
+  '50': {
+    codigo: '50',
+    descripcion: 'Error interno general',
+    motivo: 'Error inesperado en el servidor del SRI',
+    solucion: 'Espera unos minutos e intenta nuevamente. Si persiste, contacta al SRI',
+    validacion: 'RECEPCIÓN'
+  },
+  '52': {
+    codigo: '52',
+    descripcion: 'Error en diferencias',
+    motivo: 'Error en los cálculos matemáticos del comprobante',
+    solucion: 'Verifica que los subtotales, impuestos y total estén calculados correctamente',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '56': {
+    codigo: '56',
+    descripcion: 'Establecimiento cerrado',
+    motivo: 'El establecimiento desde el cual se genera el comprobante está cerrado',
+    solucion: 'Verifica el estado del establecimiento en el portal del SRI',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '57': {
+    codigo: '57',
+    descripcion: 'Autorización suspendida',
+    motivo: 'La autorización para emitir comprobantes electrónicos está suspendida',
+    solucion: 'Contacta al SRI para regularizar tu situación tributaria',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '58': {
+    codigo: '58',
+    descripcion: 'Error en la estructura de clave de acceso',
+    motivo: 'Los componentes de la clave de acceso no coinciden con los datos del comprobante',
+    solucion: 'Verifica que la fecha de emisión, RUC, establecimiento y secuencial sean correctos',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '63': {
+    codigo: '63',
+    descripcion: 'RUC clausurado',
+    motivo: 'El RUC del emisor está clausurado',
+    solucion: 'Regulariza tu situación tributaria con el SRI',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '65': {
+    codigo: '65',
+    descripcion: 'Fecha de emisión extemporánea',
+    motivo: 'El comprobante no fue enviado dentro del tiempo permitido',
+    solucion: 'Los comprobantes deben enviarse máximo 24 horas después de su emisión',
+    validacion: 'EMISOR/RECEPCIÓN'
+  },
+  '67': {
+    codigo: '67',
+    descripcion: 'Fecha inválida',
+    motivo: 'Error en el formato de la fecha',
+    solucion: 'Verifica que la fecha esté en formato dd/MM/yyyy',
+    validacion: 'RECEPCIÓN'
+  },
+  '70': {
+    codigo: '70',
+    descripcion: 'Clave de acceso en procesamiento',
+    motivo: 'El comprobante ya fue enviado y aún está siendo procesado',
+    solucion: 'Espera a que termine el procesamiento del comprobante anterior',
+    validacion: 'RECEPCIÓN'
+  },
+  '80': {
+    codigo: '80',
+    descripcion: 'Error en la estructura de clave de acceso',
+    motivo: 'La clave de acceso tiene formato incorrecto (más de 49 dígitos o caracteres no numéricos)',
+    solucion: 'Verifica que la clave de acceso tenga exactamente 49 dígitos numéricos',
+    validacion: 'AUTORIZACIÓN'
+  },
+  '82': {
+    codigo: '82',
+    descripcion: 'Error en la fecha de inicio de transporte',
+    motivo: 'La fecha de inicio de transporte es menor a la fecha de emisión',
+    solucion: 'Verifica que la fecha de inicio de transporte sea igual o posterior a la fecha de emisión',
+    validacion: 'RECEPCIÓN'
+  },
+  '92': {
+    codigo: '92',
+    descripcion: 'Error al validar monto de devolución del IVA',
+    motivo: 'El valor de devolución del IVA no corresponde al autorizado',
+    solucion: 'Verifica el monto de devolución del IVA con el servicio web DIG',
+    validacion: 'RECEPCIÓN'
+  }
+}
+
+/**
+ * Obtiene información detallada sobre un código de error del SRI
+ */
+export function getErrorInfo(codigo: string): ErrorSRI | null {
+  return ERRORES_SRI[codigo] || null
+}
+
+/**
+ * Formatea un mensaje de error del SRI de manera amigable para el usuario
+ */
+export function formatSRIError(
+  codigo: string,
+  mensajeOriginal: string,
+  infoAdicional?: string
+): string {
+  const errorInfo = getErrorInfo(codigo)
+
+  if (!errorInfo) {
+    // Si no tenemos información del código, mostrar el mensaje original
+    let mensaje = `Error ${codigo}: ${mensajeOriginal}`
+    if (infoAdicional) {
+      mensaje += `\n${infoAdicional}`
+    }
+    return mensaje
+  }
+
+  // Construir mensaje amigable
+  let mensaje = `${errorInfo.descripcion}\n\n`
+  mensaje += `Motivo: ${errorInfo.motivo}\n`
+
+  if (infoAdicional) {
+    mensaje += `Detalle (Respuesta API): ${infoAdicional}\n`
+  }
+
+  if (errorInfo.solucion) {
+    mensaje += `\nSolución: ${errorInfo.solucion}`
+  }
+
+  return mensaje
+}
+
+/**
+ * Verifica si un código de error es crítico (no se puede resolver sin intervención del SRI)
+ */
+export function isCriticalError(codigo: string): boolean {
+  const criticalCodes = ['2', '10', '27', '37', '46', '56', '57', '63']
+  return criticalCodes.includes(codigo)
+}
+
+/**
+ * Verifica si un código de error es recuperable (se puede solucionar ajustando el documento)
+ */
+export function isRecoverableError(codigo: string): boolean {
+  const recoverableCodes = ['35', '43', '45', '52', '58', '65', '67', '80']
+  return recoverableCodes.includes(codigo)
+}

+ 99 - 18
src/lib/sri-utils.ts

@@ -1,4 +1,5 @@
 import * as soap from 'soap'
+import { formatSRIError, isCriticalError, isRecoverableError } from './sri-error-codes'
 
 /**
  * Endpoints del SRI
@@ -35,26 +36,33 @@ export interface RespuestaRecepcion {
   }
 }
 
+export interface AutorizacionItem {
+  estado: string
+  numeroAutorizacion?: string
+  fechaAutorizacion?: string
+  ambiente?: string
+  comprobante?: string
+  mensajes?: {
+    mensaje: {
+      identificador: string
+      mensaje: string
+      informacionAdicional?: string
+      tipo: string
+    } | Array<{
+      identificador: string
+      mensaje: string
+      informacionAdicional?: string
+      tipo: string
+    }>
+  }
+}
+
 export interface RespuestaAutorizacion {
   RespuestaAutorizacionComprobante: {
     claveAccesoConsultada: string
     numeroComprobantes: string
     autorizaciones?: {
-      autorizacion: Array<{
-        estado: string
-        numeroAutorizacion?: string
-        fechaAutorizacion?: string
-        ambiente?: string
-        comprobante?: string
-        mensajes?: {
-          mensaje: Array<{
-            identificador: string
-            mensaje: string
-            informacionAdicional?: string
-            tipo: string
-          }>
-        }
-      }>
+      autorizacion: AutorizacionItem | AutorizacionItem[]
     }
   }
 }
@@ -94,13 +102,39 @@ export async function sendDocToSRI(
 
               if (estado !== 'RECIBIDA') {
                 console.error('Documento no recibido:', JSON.stringify(result, null, 2))
+
+                // Extraer y formatear mensajes de error
+                const mensajes = result?.RespuestaRecepcionComprobante?.comprobantes?.comprobante
+                if (mensajes && Array.isArray(mensajes)) {
+                  mensajes.forEach((comp: any) => {
+                    const msgs = comp.mensajes?.mensaje
+                    if (msgs) {
+                      const mensajesArray = Array.isArray(msgs) ? msgs : [msgs]
+                      mensajesArray.forEach((msg: any) => {
+                        const errorFormateado = formatSRIError(
+                          msg.identificador,
+                          msg.mensaje,
+                          msg.informacionAdicional
+                        )
+                        console.error('\n' + errorFormateado + '\n')
+
+                        if (isCriticalError(msg.identificador)) {
+                          console.error('⚠️  Este es un error crítico que requiere intervención administrativa')
+                        } else if (isRecoverableError(msg.identificador)) {
+                          console.error('🔧 Este error puede solucionarse corrigiendo el documento')
+                        }
+                      })
+                    }
+                  })
+                }
+
                 reject(
                   new Error(
                     `El documento no fue recibido por el SRI. Estado: ${estado || 'DESCONOCIDO'}`
                   )
                 )
               } else {
-                console.log('Documento recibido exitosamente')
+                console.log('Documento recibido exitosamente')
                 resolve(result)
               }
             }
@@ -142,6 +176,7 @@ export async function checkAuthorizationStatus(
               reject(new Error(`Error al consultar autorización: ${err.message}`))
             } else {
               console.log('Estado consultado exitosamente')
+              console.log('Respuesta completa del SRI:', JSON.stringify(result, null, 2))
               resolve(result)
             }
           })
@@ -177,16 +212,62 @@ export async function longPollDoc({
 
     try {
       const result = await checkAuthorizationStatus(accessKey, ambiente)
-      const autorizacion = result.RespuestaAutorizacionComprobante.autorizaciones?.autorizacion?.[0]
+
+      // La autorización puede venir como objeto o array
+      let autorizacion = result.RespuestaAutorizacionComprobante.autorizaciones?.autorizacion
+      if (Array.isArray(autorizacion)) {
+        autorizacion = autorizacion[0]
+      }
 
       if (autorizacion) {
         const estado = autorizacion.estado
+        console.log(`Estado actual del documento: ${estado}`)
+
+        // Si hay mensajes, mostrarlos con formato mejorado
+        if (autorizacion.mensajes?.mensaje) {
+          const mensajes = Array.isArray(autorizacion.mensajes.mensaje)
+            ? autorizacion.mensajes.mensaje
+            : [autorizacion.mensajes.mensaje]
+
+          console.log('\n📨 Mensajes del SRI:')
+          mensajes.forEach((msg: any, idx: number) => {
+            console.log(`\n--- Mensaje ${idx + 1} ---`)
+
+            // Formatear error con información detallada
+            const errorFormateado = formatSRIError(
+              msg.identificador,
+              msg.mensaje,
+              msg.informacionAdicional
+            )
+            console.log(errorFormateado)
+
+            // Indicar tipo de error
+            if (isCriticalError(msg.identificador)) {
+              console.log('\n⚠️  ERROR CRÍTICO: Requiere intervención administrativa del SRI')
+            } else if (isRecoverableError(msg.identificador)) {
+              console.log('\n🔧 ERROR RECUPERABLE: Puedes corregir el documento y volver a enviarlo')
+            }
+          })
+          console.log('\n' + '─'.repeat(50) + '\n')
+        }
 
         // Estados finales
         if (estado === 'AUTORIZADO' || estado === 'NO AUTORIZADO' || estado === 'DEVUELTA') {
-          console.log(`Estado final alcanzado: ${estado}`)
+          if (estado === 'AUTORIZADO') {
+            console.log(`✅ Estado final alcanzado: ${estado}`)
+            if (autorizacion.numeroAutorizacion) {
+              console.log(`🎫 Número de autorización: ${autorizacion.numeroAutorizacion}`)
+            }
+          } else {
+            console.log(`Estado final alcanzado: ${estado}`)
+          }
           return result
+        } else {
+          console.log(`⏳ Documento en procesamiento (estado: ${estado})`)
         }
+      } else {
+        console.log('⚠️  No se encontró información de autorización en la respuesta')
+        console.log('Número de comprobantes:', result.RespuestaAutorizacionComprobante.numeroComprobantes)
       }
 
       // Esperar antes del siguiente intento

+ 4 - 0
src/types/envio-sri.ts

@@ -54,6 +54,10 @@ export interface RespuestaVerificacionSRI {
     mensaje: string
     tipo: "INFORMATIVO" | "ADVERTENCIA" | "ERROR"
     informacionAdicional?: string
+    // Campos adicionales para mensajes formateados
+    mensajeFormateado?: string
+    esCritico?: boolean
+    esRecuperable?: boolean
   }>
   error?: string
   details?: string

+ 15 - 12
src/utils/factura/claveAcceso.ts

@@ -16,27 +16,30 @@ interface GenerateAccessKeyParams {
  * Formatea una fecha al formato DDMMYYYY
  */
 function formatDateToDDMMYYYY(dateString: string): string {
-  let date: Date
+  let day: string
+  let month: string
+  let year: string
 
   // Detectar formato de fecha
   if (dateString.includes('-')) {
-    // Formato YYYY-MM-DD
-    date = new Date(dateString)
+    // Formato YYYY-MM-DD - parsear directamente sin Date para evitar problemas de zona horaria
+    const parts = dateString.split('-')
+    year = parts[0]
+    month = parts[1]
+    day = parts[2]
   } else if (dateString.includes('/')) {
     // Formato DD/MM/YYYY
-    const [day, month, year] = dateString.split('/')
-    date = new Date(`${year}-${month}-${day}`)
+    const parts = dateString.split('/')
+    day = parts[0]
+    month = parts[1]
+    year = parts[2]
   } else {
     throw new Error('Formato de fecha no válido. Use YYYY-MM-DD o DD/MM/YYYY')
   }
 
-  const day = date.getDate()
-  const month = date.getMonth() + 1 // getMonth() returns 0-11
-  const year = date.getFullYear()
-
-  // Pad day and month with a leading zero if they are less than 10
-  const finalDay = day < 10 ? '0' + day : day.toString()
-  const finalMonth = month < 10 ? '0' + month : month.toString()
+  // Asegurar que día y mes tengan 2 dígitos
+  const finalDay = day.padStart(2, '0')
+  const finalMonth = month.padStart(2, '0')
 
   return `${finalDay}${finalMonth}${year}`
 }

+ 16 - 0
src/utils/factura/validations.ts

@@ -1,5 +1,21 @@
 // Funciones de validación específicas para Ecuador
 
+/**
+ * Convierte una fecha en formato YYYY-MM-DD al formato dd/MM/yyyy requerido por el SRI
+ * @param dateString - Fecha en formato YYYY-MM-DD (ej: "2025-10-31")
+ * @returns Fecha en formato dd/MM/yyyy (ej: "31/10/2025")
+ */
+export const formatDateForSRI = (dateString: string): string => {
+  // Si ya está en formato dd/MM/yyyy, retornar como está
+  if (/^\d{2}\/\d{2}\/\d{4}$/.test(dateString)) {
+    return dateString
+  }
+
+  // Convertir de YYYY-MM-DD a dd/MM/yyyy
+  const [year, month, day] = dateString.split('-')
+  return `${day}/${month}/${year}`
+}
+
 export const validateRUC = (ruc: string): boolean => {
   // Validación básica de RUC ecuatoriano (13 dígitos)
   return /^\d{13}$/.test(ruc)